Thread Completion
In this lesson, you will learn how you can make your program wait for a thread to complete.
We'll cover the following
The example in the previous lesson shows how to create a thread. However, what happens if you want to wait for a thread to complete? You need to do something special in order to wait for completion; in particular, you must call the routine pthread_join()
.
pthread_join()
int pthread_join(pthread_t thread, void **value_ptr);
This routine takes two arguments. The first is of type pthread_t
, and is used to specify which thread to wait for. This variable is initialized by the thread creation routine (when you pass a pointer to it as an argument to pthread_create()
); if you keep it around, you can use it to wait for that thread to terminate.
The second argument is a pointer to the return value you expect to get back. Because the routine can return anything, it is defined to return a pointer to void; because the pthread_join()
routine changes the value of the passed in argument, you need to pass in a pointer to that value, not just the value itself.
Example 1
Let’s look at an example in the code widget below. In the code, a single thread is again created and passed a couple of arguments via the myarg_t
structure. To return values, the myret_t
type is used. Once the thread is finished running, the main thread, which has been waiting inside of the pthread_join()
myret_t
.
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include "common_threads.h" 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; }
A few things to note about this example. First, oftentimes you don’t have to do all of this painful packing and unpacking of arguments. For example, if you just create a thread with no arguments, you can pass NULL
in as an argument when the thread is created. Similarly, you can pass NULL
into pthread_join()
if you don’t care about the return value.
Example 2
Second, if you are just passing in a single value (e.g., a long long int
), you don’t have to package it up as an argument.
The code widget below shows an example. In this case, life is a bit simpler, as you don’t have to package arguments and return values inside of structures.
#include <stdio.h> #include <pthread.h> #include "common_threads.h" void *mythread(void *arg) { long long int value = (long long int) arg; printf("%lld\n", value); return (void *) (value + 1); } int main(int argc, char *argv[]) { pthread_t p; long long int rvalue; Pthread_create(&p, NULL, mythread, (void *) 100); Pthread_join(p, (void **) &rvalue); printf("returned %lld\n", rvalue); return 0; }
Third, you should note that one has to be extremely careful with how values are returned from a thread. Specifically, never return a pointer which refers to something allocated on the thread’s call stack. If you do, what do you think will happen? (think about it!) Here is an example of a dangerous piece of code, modified from the first example of this lesson.
void *mythread(void *arg) {myarg_t *args = (myarg_t *) arg;printf("%d %d\n", args->a, args->b);myret_t oops; // ALLOCATED ON STACK: BAD!oops.x = 1;oops.y = 2;return (void *) &oops;}
In this case, the variable oops
is allocated on the stack of mythread
. However, when it returns, the value is automatically deallocated (that’s why the stack is so easy to use, after all!), and thus, passing back a pointer to a now deallocated variable will lead to all sorts of bad results. Certainly, when you print out the values you think you returned, you’ll probably (but not necessarily!) be surprised. Try it and
Finally, you might notice that the use of pthread_create()
to create a thread, followed by an immediate call to pthread_join()
, is a pretty strange way to create a thread. In fact, there is an easier way to accomplish this exact task; it’s called a procedure call. Clearly, you’ll usually be creating more than just one thread and waiting for it to complete, otherwise, there is not much purpose to using threads at all.
You should note that not all code that is multi-threaded uses the join routine. For example, a multi-threaded web server might create a number of worker threads, and then use the main thread to accept requests and pass them to the workers, indefinitely. Such long-lived programs thus may not need to join. However, a parallel program that creates threads to execute a particular task (in parallel) will likely use join to make sure all such work completes before exiting or moving onto the next stage of computation.
Get hands-on with 1400+ tech skills courses.