THREAD(3)

πŸ“¦
version 0_git20260208 Β· 2 versions
, in alpine 3.24, arch rolling, nixos 26.05, nixpkgs unstable

πŸ¦‹ THREAD πŸ’… (that's the THREAD(3) manual page, bestie)

okay so grab a snack and settle in because this is the page β€” the one about threads and procs in Plan 9 land, from the library functions manual. It's long, so it's coming at you in three parts, and this is part 1. Don't go anywhere. πŸ“


🌸 NAME 🌸

Say hi to the whole friend group (yes, all of them, in order, no one gets left out):

alt, chancreate, chanfree, chanprint, chansetname, mainstacksize, proccreate, procdata, recv, recvp, recvul,

send, sendp, sendul, nbrecv, nbrecvp, nbrecvul, nbsend, nbsendp, nbsendul, threadcreate, threaddata,

threadexec, threadexecl, threadexits, threadexitsall, threadgetgrp, threadgetname, threadint, threadintgrp, threadkill, threadkillgrp,

threadmain, threadmaybackground, threadnotify, threadid, threadpid, threadpin, threadunpin, threadsetgrp, threadsetname, threadsetstate,

threadspawn, threadspawnd, threadspawnl, threadwaitchan, yield

β€” and what is all this for? thread and proc management, periodt. 🫢

ngl, some of these are the ✨main character✨ functions and some are the supporting cast, but the man page is gonna walk you through every single one. No skipping. πŸ’€

✨ SYNOPSIS ✨

first things first, the headers you gotta pull in. it's giving "unlock the door before you walk in":

#include <u.h>
#include <libc.h>
#include <thread.h>

πŸŽ€ the Alt constants (the vibes table)

these little defines are how you tell alt what you want each case to do β€” copy them exactly, they are literally just numbers under the glitter:

#define CHANEND     0
#define CHANSND     1
#define CHANRCV     2
#define CHANNOP     3
#define CHANNOBLK   4

🧚 the Alt struct

this is the shape of one row in an alt β€” every field, every type, untouched, iykyk:

typedef struct Alt Alt;
struct Alt {
    Channel *c;
    void    *v;
    int     op;
    Channel **tag;
    int     entryno;
    char    *name;
};

πŸ’– the function lineup

thread life & death (the whole arc, honestly)

void     threadmain(int argc, char *argv[])
int      threadmaybackground(void)
int      mainstacksize
int      proccreate(void (*fn)(void*), void *arg, uint stacksize)
int      threadcreate(void (*fn)(void*), void *arg, uint stacksize)
void     threadexits(char *status)
void     threadexitsall(char *status)
void     yield(void)
int      threadpin(void)
int      threadunpin(void)

identity crisis? no bestie, we have ids for that πŸͺͺ

int      threadid(void)
int      threadgrp(void)
int      threadsetgrp(int group)
int      threadpid(int id)

the "please stop" department 😭

int      threadint(int id)
int      threadintgrp(int group)
int      threadkill(int id)
int      threadkillgrp(int group)

names & data (so you know who's who)

void     threadsetname(char *name)
char*    threadgetname(void)

void**   threaddata(void)
void**   procdata(void)

channels β€” the group chat itself πŸ“¨

Channel* chancreate(int elsize, int nel)
void     chanfree(Channel *c)

sending, receiving, and the legendary alt

int      alt(Alt *alts)
int      recv(Channel *c, void *v)
void*    recvp(Channel *c)
ulong    recvul(Channel *c)
int      nbrecv(Channel *c, void *v)
void*    nbrecvp(Channel *c)
ulong    nbrecvul(Channel *c)
int      send(Channel *c, void *v)
int      sendp(Channel *c, void *v)
int      sendul(Channel *c, ulong v)
int      nbsend(Channel *c, void *v)
int      nbsendp(Channel *c, void *v)
int      nbsendul(Channel *c, ulong v)
int      chanprint(Channel *c, char *fmt, ...)

spawning & exec'ing whole other programs, icon behaviour πŸ‘‘

int      threadspawnl(int fd[3], char *file, ...)
int      threadspawn(int fd[3], char *file, char *args[])
int      threadspawnd(int fd[3], char *file, char *args[], char *dir)
int      threadexecl(Channel *cpid, int fd[3], char *file, ...)
int      threadexec(Channel *cpid, int fd[3], char *file, char *args[])
Channel* threadwaitchan(void)

and one tiny notify hook

int      threadnotify(int (*f)(void*, char*), int in)

that's the whole cast list and every signature, character for character β€” zero notes, zero vibes lost. 🌈

part 2 is literally right there waiting for you, so flip the page, bestie β€” the DESCRIPTION is about to eat, no cap. πŸ”₯

🌸 DESCRIPTION 🌸

okay so bestie, the thread library hands you parallel programming support that's lowkey similar to what the languages Alef and Newsqueak give you. Threads and procs live in a shared address space, and they communicate + synchronize through channels and shared variables. iconic honestly.

A proc is a Plan 9 process that contains one or more cooperatively scheduled threads. Programs using threads must replace main by threadmain. The thread library provides a main function that sets up a proc with a single thread executing threadmain. yes bestie, that's the whole vibe.

Every thread is backed by an operating system-provided pthread and runs on its system-provided stack; mainstacksize and the stack size arguments to proccreate and threadcreate are ignored. Even though each thread is backed by a separate pthread, the threads in a proc are still scheduled non-preemptively as on Plan 9 and as described below. no cap.

  • threadcreate β€” creates a new thread in the calling proc, returning a unique integer identifying the thread; the thread executes fn(arg) on a stack of size stacksize. Thread stacks are allocated in shared memory, making it valid to pass pointers to stack variables between threads and procs. ✨
  • proccreate β€” creates a new proc, and inside that proc creates a single thread as threadcreate would, returning the id of the created thread. Be aware that the calling thread may continue execution before the newly created proc and thread are scheduled. Because of this, arg should not point to data on the stack of a function that could return before the new process is scheduled. 🫢
  • threadexits β€” terminates the calling thread. If the thread is the last in its proc, threadexits also terminates the proc, using status as the exit status.
  • threadexitsall β€” terminates all procs in the program, using status as the exit status.

When the last thread in threadmain's proc exits, the program will appear to its parent to have exited. The remaining procs will still run together, but as a background program. This functionality can only be relied upon if the program defines a function threadmaybackground returning a non-zero result. Programs that do not define such a threadmaybackground will crash instead should the last thread in threadmain's proc exit leaving behind other running procs. 😭

bestie please read that again β€” no threadmaybackground, no safe backgrounding. it's giving "crash era" if you ignore it.

The threads in a proc are coroutines, scheduled nonpreemptively in a round-robin fashion. A thread must explicitly relinquish control of the processor before another thread in the same proc is run. Calls that do this are yield, proccreate, threadexec, threadexecl, threadexits, threadspawn, threadspawnd, threadspawnl, alt, send, and recv (and the calls related to send and recvβ€”see their descriptions further on). Procs are scheduled by the operating system. Therefore, threads in different procs can preempt one another in arbitrary ways and should synchronize their actions using qlocks (see lock(3)) or channel communication. System calls such as read(3) block the entire proc; all threads in a proc block until the system call finishes. πŸ’…

threadpin disables scheduling inside a proc, pinning the current thread as the only runnable one in the current proc. threadunpin reenables scheduling, allowing other procs to run once the current thread relinquishes the processor. threadpin and threadunpin can lead to deadlock. Used carefully, they can make library routines that use qlocks appear atomic relative to the current proc, like a system call. ⚠️

As mentioned above, each thread has a unique integer thread id. Thread ids are not reused; they are unique across the life of the program. threadid returns the id for the current thread. Each thread also has a thread group id. The initial thread has a group id of zero. Each new thread inherits the group id of the thread that created it. threadgrp returns the group id for the current thread; threadsetgrp sets it. threadpid returns the pid of the Plan 9 process containing the thread identified by id, or -1 if no such thread is found. πŸŽ€

threadint interrupts a thread that is blocked in a channel operation or system call. threadintgrp interrupts all threads with the given group id. threadkill marks a thread to die when it next relinquishes the processor (via one of the calls listed above). If the thread is blocked in a channel operation or system call, it is also interrupted. threadkillgrp kills all threads with the given group id. Note that threadkill and threadkillgrp will not terminate a thread that never relinquishes the processor. πŸ’€

Primarily for debugging, threads can have string names associated with them. threadgetname returns the current thread's name; threadsetname sets it. The pointer returned by threadgetname is only valid until the next call to threadsetname. 🏷️

Also for debugging, threads have a string state associated with them. threadsetstate sets the state string. There is no threadgetstate; since the thread scheduler resets the state to Running every time it runs the thread, it is only useful for debuggers to inspect the state. πŸ”

threaddata returns a pointer to a per-thread pointer that may be modified by threaded programs for per-thread storage. Similarly, procdata returns a pointer to a per-proc pointer. πŸ—‚οΈ

threadexecl and threadexec are threaded analogues of exec and execl (see exec(3)); on success, they replace the calling thread and invoke the external program, never returning. (Unlike on Plan 9, the calling thread need not be the only thread in its procβ€”the other threads will continue executing.) On error, they return -1. If cpid is not null, the pid of the invoked program will be sent along cpid (using sendul) once the program has been started, or -1 will be sent if an error occurs. threadexec and threadexecl will not access their arguments after sending a result along cpid. Thus, programs that malloc the argv passed to threadexec can safely free it once they have received the cpid response. πŸš€

threadexecl and threadexec will duplicate (see dup(3)) the three file descriptors in fd onto standard input, output, and error for the external program and then close them in the calling thread. Beware of code that sets

fd[0] = 0;
fd[1] = 1;
fd[2] = 2;

to use the current standard files. The correct code is

fd[0] = dup(0, -1);
fd[1] = dup(1, -1);
fd[2] = dup(2, -1);
bestie do not be out here assigning 0, 1, 2 directly. that's the trap. use the dup version, periodt. πŸ’…

threadspawnl and threadspawn are like threadexecl and threadexec but do not replace the current thread. They return the pid of the invoked program on success, or -1 on error. threadspawnd is like threadspawn but takes as its final argument the directory in which to run the invoked program. The child will attempt to change into that directory before running the program, but it is only best effort: failure to change into the directory does not stop the running of the program. 🌈

threadwaitchan returns a channel of pointers to Waitmsg structures (see wait(3)). When an exec'ed process exits, a pointer to a Waitmsg is sent to this channel. These Waitmsg structures have been allocated with malloc(3) and should be freed after use. 🧚

A Channel is a buffered or unbuffered queue for fixed-size messages. Procs and threads send messages into the channel and recv messages from the channel. If the channel is unbuffered, a send operation blocks until the corresponding recv operation occurs and vice versa. chancreate allocates a new channel for messages of size elsize and with a buffer holding nel messages. If nel is zero, the channel is unbuffered. chanfree frees a channel that is no longer used. chanfree can be called by either sender or receiver after the last item has been sent or received. Freeing the channel will be delayed if there is a thread blocked on it until that thread unblocks (but chanfree returns immediately). πŸ¦‹

The name element in the Channel structure is a description intended for use in debugging. chansetname sets the name. 🏷️

send sends the element pointed at by v to the channel c. If v is null, zeros are sent. recv receives an element from c and stores it in v. If v is null, the received value is discarded. send and recv return 1 on success, -1 if interrupted. nbsend and nbrecv behave similarly, but return 0 rather than blocking. πŸ“¨

sendp, nbsendp, sendul, and nbsendul send a pointer or an unsigned long; the channel must have been initialized with the appropriate elsize. recvp, nbrecvp, recvul, and nbrecvul receive a pointer or an unsigned long; they return zero when a zero is received, when interrupted, or (for nbrecvp and nbrecvul) when the operation would have blocked. To distinguish between these three cases, use recv or nbrecv. πŸ”’

alt can be used to recv from or send to one of a number of channels, as directed by an array of Alt structures, each of which describes a potential send or receive operation. In an Alt structure, c is the channel; v the value pointer (which may be null); and op the operation: CHANSND for a send operation, CHANRECV for a recv operation; CHANNOP for no operation (useful when alt is called with a varying set of operations). The array of Alt structures is terminated by an entry with op CHANEND or CHANNOBLK. If at least one Alt structure can proceed, one of them is chosen at random to be executed. alt returns the index of the chosen structure. If no operations can proceed and the list is terminated with CHANNOBLK, alt returns the index of the terminating CHANNOBLK structure. Otherwise, alt blocks until one of the operations can proceed, eventually returning the index of the structure executes. alt returns -1 when interrupted. The tag and entryno fields in the Alt structure are used internally by alt and need not be initialized. They are not used between alt calls. 🎲

chanprint formats its arguments in the manner of print(3) and sends the result to the channel c. The string delivered by chanprint is allocated with malloc(3) and should be freed upon receipt. πŸ“

Thread library functions do not return on failure; if errors occur, the entire program is aborted. 😭

Threaded programs should use threadnotify in place of atnotify (see notify(3)). πŸ””

It is safe to use sysfatal(3) in threaded programs. sysfatal will print the error string and call threadexitsall. 🫢

It is not safe to call rfork in a threaded program, except to call rfork(RFNOTEG) from the main proc before any other procs have been created. To create new processes, use proccreate. ⚠️


πŸŽ€ FILES πŸŽ€

  • /acid/thread contains useful acid(1) functions for debugging threaded programs. πŸ”
  • /src/libthread/test contains some example programs. πŸ§ͺ

πŸ¦‹ SOURCE πŸ¦‹

  • /src/libthread

πŸ’œ SEE ALSO πŸ’œ

πŸ’€ BUGS πŸ’€

Okay bestie, listen up, because this is the part where the manpage gets real with us 🫢

  • The way that name conflicts are avoided is... macros, bestie. Not everyone's fave. alt, nbrecv, nbrecvp, nbrecvul, nbsend, nbsendp, nbsendul, recv, recvp, recvul, send, sendp, and sendul are all defined as macros that expand to chanalt, channbrecv, and so on. Same deal for yield β€” it's a macro that expands to threadyield. Go read intro(3) for the whole story, iykyk ✨
  • Unimplemented besties alert πŸ’€ Threadint, threadintgrp, threadkill, threadkillgrp and threadpid are literally just... not implemented. Not there. No thoughts, head empty πŸ™ˆ
  • The implementation of threadnotify may not be correct πŸ‘€ like, it might be fine, it might not be. Vibe check inconclusive.
⚠️ Warning time, and this one's a real one: there appears to be a race in the Linux NPTL implementation of pthread_exit. So instead of coordinating a simultaneous threadexits among many threads (pls don't, it's giving chaos 😭), call threadexitsall instead. Trust. 🧚

And that's the whole page, bestie!! You made it to the end, I'm so proud of you πŸ’–πŸŽ€πŸ¦‹ This was the THREAD(3) manual page β€” section 3, the library calls girlies β€” so go forth and thread responsibly. Bye bestie, slay, periodt πŸŒΈπŸ’…πŸ‘‘