Joining a thread means waiting for it to terminate. It can be seen as a specific usage of condition variables.
The pthread_join subroutine provides a simple mechanism allowing a thread to wait for another thread to terminate. More complex conditions, such as waiting for multiple threads to terminate, can be implemented by the programmer using condition variables. See " Synchronizing Threads with Condition Variables for more information.
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. Once a target thread has been joined, it is automatically detached, and its storage can be reclaimed.
The following table indicates the two possible cases when a thread calls the pthread_join subroutine, depending on the state and the detachstate attribute of the target thread.
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. |
A thread cannot join itself - a deadlock would occur and it is detected by the library. However, two threads may try to join each other; they will deadlock. This situation is not detected by the library.
It is possible for several threads to 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.
This figure illustrates the two possible cases.
The following example is an enhanced version of the first multi-threaded program. The program ends after exactly five messages in each language are displayed. This is done by blocking the initial thread until the "writer" threads exit.
#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); }
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. This pointer is stored as long as the thread is not detached, and the pthread_join subroutine can return it.
For example, a multi-threaded grep command may choose the following implementation. The initial thread creates one thread per file to scan, each thread having the same entry point routine. It then waits for all threads to be terminated. Each "scanning" thread stores the found lines in a dynamically allocated buffer and returns a pointer to this buffer. The initial thread prints out each buffer and frees 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. 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. Care must be taken concerning the storage class of the data the pointer refers to. The pointer must be still valid after the thread was terminated and its storage reclaimed. Therefore, returning a thread-specific data value should be avoided, because the destructor routine is called when the thread's storage is reclaimed.
Returning a pointer to dynamically allocated storage to several threads should also be handled with care. Consider the following code fragment:
void *returned_data; ... pthread_join(target_thread, &returned_data); /* retrieves information from returned_data */ free(returned_data);
When executed by only one thread, the returned_data pointer is freed as it should be. If several threads execute this code fragment concurrently, the returned_data pointer is freed several times; this must be avoided. A solution may consist in using a flag, protected by a mutex, to signal that the returned_data pointer was freed. The line:
free(returned_data);
would thus be replaced by the lines (assuming the flag variable is initially 0)
/* 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 */
where a mutex can be used for locking the access to the critical region. This 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.
List of Synchronization Subroutines.