Joining threads
Joining a thread means waiting for it to terminate, which can be seen as a specific usage of condition variables.
Waiting for a thread
Using the pthread_join subroutine alows a thread to wait for another thread to terminate. More complex conditions, such as waiting for multiple threads to terminate, can be implemented by using condition variables.
Calling the pthread_join subroutine
The pthread_join subroutine blocks the calling thread until the specified thread terminates. The target thread (the thread whose termination is awaited) must not be detached. If the target thread is already terminated, but not detached, the pthread_join subroutine returns immediately. After a target thread has been joined, it is automatically detached, and its storage can be reclaimed.
The following table indicates the possible cases when a thread calls the pthread_join subroutine, depending on the state and the detachstate attribute of the target thread.
Target State | Undetached target | Detached target |
---|---|---|
Target is still running | The caller is blocked until the target is terminated. | The call returns immediately, indicating an error. |
Target is terminated | The call returns immediately, indicating a successful completion. |
Multiple joins
Several threads can join the same target thread, if the target is not detached. The success of this operation depends on the order of the calls to the pthread_join subroutine and the moment when the target thread terminates.
- Any call to the pthread_join subroutine occurring before the target thread's termination blocks the calling thread.
- When the target thread terminates, all blocked threads are awoken, and the target thread is automatically detached.
- Any call to the pthread_join subroutine occurring after the target thread's termination will fail, because the thread is detached by the previous join.
- If no thread called the pthread_join subroutine before the target thread's termination, the first call to the pthread_join subroutine will return immediately, indicating a successful completion, and any further call will fail.
Join example
#include <pthread.h> /* include file for pthreads - the 1st */
#include <stdio.h> /* include file for printf() */
void *Thread(void *string)
{
int i;
/* writes five messages and exits */
for (i=0; i<5; i++)
printf("%s\n", (char *)string);
pthread_exit(NULL);
}
int main()
{
char *e_str = "Hello!";
char *f_str = "Bonjour !";
pthread_attr_t attr;
pthread_t e_th;
pthread_t f_th;
int rc;
/* creates the right attribute */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,
PTHREAD_CREATE_UNDETACHED);
/* creates both threads */
rc = pthread_create(&e_th, &attr, Thread, (void *)e_str);
if (rc)
exit(-1);
rc = pthread_create(&f_th, &attr, Thread, (void *)f_str);
if (rc)
exit(-1);
pthread_attr_destroy(&attr);
/* joins the threads */
pthread_join(e_th, NULL);
pthread_join(f_th, NULL);
pthread_exit(NULL);
}
A thread cannot join itself because a deadlock would occur and it is detected by the library. However, two threads may try to join each other. They will deadlock, but this situation is not detected by the library.
Returning information from a thread
The pthread_join subroutine also allows a thread to return information to another thread. When a thread calls the pthread_exit subroutine or when it returns from its entry-point routine, it returns a pointer (see Exiting a Thread). This pointer is stored as long as the thread is not detached, and the pthread_join subroutine can return it.
/* "scanning" thread */
...
buffer = malloc(...);
/* finds the search pattern in the file
and stores the lines in the buffer */
return (buffer);
/* initial thread */
...
for (/* each created thread */) {
void *buf;
pthread_join(thread, &buf);
if (buf != NULL) {
/* print all the lines in the buffer,
preceded by the filename of the thread */
free(buf);
}
}
...
If the target thread is canceled, the pthread_join subroutine returns a value of -1 cast into a pointer (see Canceling a Thread). Because -1 cannot be a pointer value, getting -1 as returned pointer from a thread means that the thread was canceled.
The returned pointer can point to any kind of data. The pointer must still be valid after the thread was terminated and its storage reclaimed. Therefore, avoid returning a value, because the destructor routine is called when the thread's storage is reclaimed.
void *returned_data;
...
pthread_join(target_thread, &returned_data);
/* retrieves information from returned_data */
free(returned_data);
free(returned_data);
/* lock - entering a critical region, no other thread should
run this portion of code concurrently */
if (!flag) {
free(returned_data);
flag = 1;
}
/* unlock - exiting the critical region */
Locking access to the critical region ensures that the returned_data pointer is freed only once.
When returning a pointer to dynamically allocated storage to several threads all executing different code, you must ensure that exactly one thread frees the pointer.