Threads exist; now, the levers. This chapter is the reference card β the concepts arrive slowly (with many examples) in the chapters that follow.
The Crux: How To Create And Control Threads
What interfaces should the OS present for thread creation and control? How should these interfaces be designed to enable ease of use as well as utility?27.1 Thread Creation
In POSIX, one call:
#include <pthread.h>
int
pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*),
void *arg);
The declaration looks complex (function pointers, if you havenβt met them, take a breath) but decomposes cleanly:
| what it is | |
|---|---|
| thread | pthread_t * β the handle, initialized by the call |
| attr | const pthread_attr_t * β attributes (stack size, scheduling priorityβ¦) |
| start_routine | void *(*)(void *) β a function pointer: where the thread begins |
| arg | void * β the argument handed to start_routine |
Packing arguments, the standard way (Figure 27.1):
typedef struct { int a; int b; } myarg_t;
void *mythread(void *arg) {
myarg_t *args = (myarg_t *) arg;
printf("%d %d\n", args->a, args->b);
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t p;
myarg_t args = { 10, 20 };
int rc = pthread_create(&p, NULL, mythread, &args);
...
}
And there it is: another live executing entity, with its own call stack, in the same address space as every other thread in the program. The fun thus begins!
27.2 Thread Completion
To wait for a thread, join join pthread_join(thread, &retval): block until the named thread completes, optionally collecting its void* return value. Long-lived servers may never join; parallel task programs join to know the work is done. (create-then-immediately-join has a simpler name: a procedure call.) defined in ch. 27 β open in glossary it:
int pthread_join(pthread_t thread, void **value_ptr);
The first argument names the thread (the handle create initialized); the second is a pointer to your void* β join writes the threadβs return value through it (pass NULL if you donβt care). The full round-trip (Figure 27.2):
typedef struct { int a; int b; } myarg_t;
typedef struct { int x; int y; } myret_t;
void *mythread(void *arg) {
myret_t *rvals = Malloc(sizeof(myret_t));
rvals->x = 1;
rvals->y = 2;
return (void *) rvals;
}
int main(int argc, char *argv[]) {
pthread_t p;
myret_t *rvals;
myarg_t args = { 10, 20 };
Pthread_create(&p, NULL, mythread, &args);
Pthread_join(p, (void **) &rvals);
printf("returned %d %d\n", rvals->x, rvals->y);
free(rvals);
return 0;
}
(Those capitalized Pthread_* calls are assert-on-failure wrappers β
a convention worth copying.) For single word-sized values, skip the
structs and cast straight through the pointer (Figure 27.3):
void *mythread(void *arg) {
long long int value = (long long int) arg;
printf("%lld\n", value);
return (void *) (value + 1);
}
One danger deserves its own walkthrough β returning a pointer to the threadβs own stack:
1The thread builds its result⦠on its own stack
mythread declares myret_t oops as a local β so it lives in the thread's stack frame. The thread returns &oops. Looks plausible; compiles (with a warning you should have heeded).
Finally, two structural notes. Creating a thread and immediately joining it is a pretty strange way to create a thread β thereβs an easier way to accomplish that exact task, called a procedure call. And not all threaded code joins at all: a web serverβs main thread may dispatch to workers indefinitely, while a parallel program joins at each stage boundary to know the work is done before moving on.
Check yourself
1.Why does pthread_create take a void* argument and expect the thread routine to return a void*?
2.What does pthread_join do, and why is its second argument a void** rather than a void*?
3.The 'oops' example returns &oops, where oops is a local variable in the thread function. What happens, and what's the rule?
4.The book jokes that pthread_create followed immediately by pthread_join is 'a pretty strange way to create a thread.' What's the punchline, and the real lesson?
5.Which programs typically SKIP pthread_join altogether β and which must use it?