Joining

Imagine an application where a thread (typically, main) has spawned off several other worker threads. Each worker thread has a specific job to do; once done, it terminates (via pthread_exit(3)). How will the creator thread know when a worker thread is done (terminated)? Ah, that is precisely where joining comes in. With the join, the creator thread can wait for, or block upon, the death (termination) of another thread within the process!

Does this not sound very much like the wait(2) system call that a parent process issues to wait for the death of a child? True, but as we shall see shortly, it's certainly not identical. 

Also, importantly, the return value from the thread that terminated is passed along to the thread that issued the join upon it. This way, it comes to know whether the worker succeeded in its task or not (and if not, the failure value can be examined to pinpoint the cause of failure):

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

The first parameter to pthread_join(3)thread, is the ID of the thread to wait for. The moment it terminates, the calling thread will receive, in the second parameter (yes, it's a value-result style parameter), the return value from the thread that terminated—which, of course is the value passed via its pthread_exit(3) call.

Thus, the join is very helpful; using this construct, you can ensure that a thread can block upon the termination of any given thread. Specifically, in the case of the main thread, we often use this mechanism to ensure that main waits for all other application threads to terminate before it itself terminates (thus preventing the zombie we saw earlier). This is considered the right approach.

Recall that in the earlier section, The return of the ghost, we clearly saw how the main thread, dying before its counterparts, becomes an inadvertent zombie (the ch14/pthreads3.c program). A quick example, built upon this previous code, will help clarify things. So, let's enhance that program – we shall now call it ch14/pthreads_joiner1.c – so that we have the main thread wait for all other threads to die by invoking the pthread_join(3) API on each of the worker threads, and only then itself terminate:

int main(void)
{
long i;
int ret, stat=0;
pthread_t tid[NTHREADS];
pthread_attr_t attr;

/* Init the thread attribute structure to defaults */
pthread_attr_init(&attr);
/* Create all threads as joinable */
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

// Thread creation loop
for (i = 0; i < NTHREADS; i++) {
printf("main: creating thread #%ld ... ", i);
ret = pthread_create(&tid[i], &attr, worker, (void *)i);
if (ret)
FATAL("pthread_create() failed! [%d] ", ret);
}
pthread_attr_destroy(&attr);

There are a few things to notice here:

  • To perform the join subsequently, we require each thread's ID; hence, we declare an array of pthread_t (the tid variable). Each element will store the corresponding thread's ID value.
  • Thread attributes:
    • Until now, we have not explicitly initialized and made use of a thread attribute structure when creating threads. Here, we rectify this shortcoming. pthread_attr_init(3) is used to initialize (to defaults) an attribute structure.
    • Furthermore, we explicitly make the threads joinable by setting up this attribute within the structure (via the pthread_attr_setdetachstate(3) API).
    • Once the threads are created, we must destroy the thread attribute structure (via the pthread_attr_destroy(3) API).

It is key to understand that only threads that have their detach state set as joinable can be joined upon. Interestingly, a joinable thread can later be set to the detached state (by calling the pthread_detach(3) API upon it); there is no converse routine.

The code continues; we now show you the thread worker function:

void * worker(void *data)
{
long datum = (long)data;
int slptm=8;

printf(" worker #%ld: will sleep for %ds now ... ", datum, slptm);
sleep(slptm);
printf(" worker #%ld: work (eyeroll) done, exiting now ", datum);

/* Terminate with success: status value 0.
* The join will pick this up. */
pthread_exit((void *)0);
}

Easy: we just have the so-called worker threads sleep for 8 seconds and then die; the pthread_exit(3), this time, passes the return status 0 as a parameter. In the following code snippet, we continue the code of main:

  // Thread join loop
for (i = 0; i < NTHREADS; i++) {
printf("main: joining (waiting) upon thread #%ld ... ", i);
ret = pthread_join(tid[i], (void **)&stat);
if (ret)
WARN("pthread_join() failed! [%d] ", ret);
else
printf("Thread #%ld successfully joined; it terminated with"
"status=%d ", i, stat);
}
printf(" main: now dying... <Dramatic!> Farewell! ");
pthread_exit(NULL);
}

Here's the key part: in a loop, the main thread blocks (waits) upon the death of each worker thread via the pthread_join(3) API; the second (value-result style) parameter, in effect, returns the status of the thread that just terminated. The usual zero-upon-success convention is followed, thus allowing the main thread to figure out whether the worker threads completed their work successfully or not. 

Let's build and run it:

$ make pthreads_joiner1 
gcc -O2 -Wall -UDEBUG -c ../common.c -o common.o
gcc -O2 -Wall -UDEBUG -c pthreads_joiner1.c -o pthreads_joiner1.o
gcc -o pthreads_joiner1 pthreads_joiner1.o common.o -lpthread
$ ./pthreads_joiner1
main: creating thread #0 ...
main: creating thread #1 ...
worker #0: will sleep for 8s now ...
main: creating thread #2 ...
worker #1: will sleep for 8s now ...
main: joining (waiting) upon thread #0 ...
worker #2: will sleep for 8s now ...

<< ... worker threads sleep for 8s ... >>

worker #0: work (eyeroll) done, exiting now
worker #1: work (eyeroll) done, exiting now
worker #2: work (eyeroll) done, exiting now
Thread #0 successfully joined; it terminated with status=0
main: joining (waiting) upon thread #1 ...
Thread #1 successfully joined; it terminated with status=0
main: joining (waiting) upon thread #2 ...
Thread #2 successfully joined; it terminated with status=0

main: now dying... <Dramatic!> Farewell!
$

As the worker threads die, they are picked up, or joined, by the main thread via pthread_join; not only that, their termination status—return value—can be examined.

Okay, we'll make a copy of the preceding program and call it ch14/pthreads_joiner2.c. The only change we make is instead of having each worker thread sleep for an identical 8 seconds, we'll make the sleep time dynamic. We will change the code; for instance, this line would be changed:sleep(slptm);

The new line would read as follows: sleep(slptm-datum);

Here, datum is the value passed to the thread—the loop index. This way, we find that the worker threads sleep as follows:

  • Worker thread #0 sleeps for (8-0) = 8 seconds
  • Worker thread #1 sleeps for (8-1) = 7 seconds
  • Worker thread #2 sleeps for (8-2) = 6 seconds

Obviously, worker thread #2 will terminate first; so what? Well, think about it: in the meantime, the main thread is looping around pthread_join, but in the order of thread #0, thread #1, thread #2. Now, thread #0 will die last and thread #2 will die first. Will this be an issue? 

Let's try it out and see:

$ ./pthreads_joiner2 
main: creating thread #0 ...
main: creating thread #1 ...
main: creating thread #2 ...
main: joining (waiting) upon thread #0 ...
worker #0: will sleep for 8s now ...
worker #1: will sleep for 7s now ...
worker #2: will sleep for 6s now ...

<< ... worker threads sleep for 8s, 7s and 6s resp ... >>

worker #2: work (eyeroll) done, exiting now
worker #1: work (eyeroll) done, exiting now
worker #0: work (eyeroll) done, exiting now
Thread #0 successfully joined; it terminated with status=0
main: joining (waiting) upon thread #1 ...
Thread #1 successfully joined; it terminated with status=0
main: joining (waiting) upon thread #2 ...
Thread #2 successfully joined; it terminated with status=0

main: now dying... <Dramatic!> Farewell!
$

What do we notice? In spite of worker thread #2 dying first, worker thread #0 gets joined first because, in the code, that is the thread we wait for first!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.21.160