C Programming

Pthread_Join() Function in C Language with Single and Multiple Thread Examples

The C language offers the possibility to develop the multitasking programs using the pthread library of the POSIX standard. This method increases the speed of the finished program by executing multiple threads in parallel with the main function.

To create a successful program using this programming method, you must make some special considerations. For example, if the execution times of a thread are longer than those of the main() function and it is fully executed, the program will terminate and so will all the associated threads, whether they have completed their task or not.

This can cause critical problems because a thread that terminates unexpectedly could be in the process of saving a user-generated data, writing to a file or device, or performing some other important task.

In this Linux Hint article, you will learn how to use the pthread_join() function to ensure that one or more threads are reliably terminated before a program exits or resumes.

Syntax of the Pthread_Join() Function in C Language

int pthread_join(pthread_t thread, void **retval);

Description of the Pthread_Join() Function in C Language

The pthread_join() function waits for the process from which it is called until a thread has finished its task. This function suspends the function from which it is called until the sub-process that is specified by its identifier in the thread input argument ends.

Once the thread finishes its task, the program resumes the execution from the next line of the code that calls the pthread_join().

When the sub-process ends, the pthread_join() function returns the exit status of the thread in retval argument.

Next, we see the input arguments to the pthread_join() along with a description of the function that satisfies each of those arguments:

thread: This input argument is a data value of the pthread_t type and is the identifier of the subprocess that the pthread_join() function should expect.

The value of the identifier is generated by the system and is obtained as a result of the pthread_create() function in its output argument thread when the thread is created.

retval: This input argument is a pointer to (void *) where the pthread_join() function stores the exit status of the thread.

If pthread_join() returns successfully, it returns 0 as the result. If an error occurs, this function returns an integer with a value which represents the error that occurred. Later on, you will see a special section which describes all the errors that this function can generate and how to identify them.

The pthread_join() function is defined in the pthread.h header. To use it, the following headers must be included in the “.c” file, as shown in the following:

#include <unistd.h>
#include <pthread.h>

NOTE: It is important that you use this feature only when necessary, as you can extend the execution time of a program by stopping one process to wait for the other to complete.

Compilation Errors in Programs with Threads

When compiling the GCC programs that use threads, the compilation may fail if it is not done correctly from the command line.

The most common error message that is issued by the compiler states that one of the thread functions that we refer to in the code is not defined.

These errors often result in wasting a valuable time in checking the headers that we inserted in the code, their integrity, and the directories that are associated with the compiler since everything indicates that the problem is there.

Although the functions that cause the error are defined in the “pthread.h” header and are included in the code, the compiler does not recognize these functions unless the pthread library is called from the command line during compilation.

In the following illustration, you can see the correct way to call the pthread library (highlighted in green) from the command console during compilation of programs with threads:

~$ gcc -pthread path/filename.c -o out_name

As we can see in the following figure, the error disappears when the pthread library is called during compilation.

How to Use the Pthread_Join() Function to Wait for a Thread to Complete the Execution in the C Language

In this example, we create a thread that has a longer execution time than the main() function. After we create the thread, we use the pthread_join() to wait for the entire execution of the thread before exiting the main() function and terminating the program.

For this purpose, we create the thread_f() function that executes the thread. This function is a “for” loop with 5 cycles of one second duration, each of which prints the “Seconds to end of thread:” message, followed by the number of remaining seconds in the command console.

From the main() function, we create the thread_1 with the pthread_create() function. Then, we call the pthread_join() function to wait for the subprocess to complete the execution before exiting the program.

The pthread_join() function call goes with the thread_1 identifier as the first input argument and the retval as NULL in the second argument. We see the following complete code for this example:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void* thread_f(void* n);

int main()
{
  pthread_t thread_1;
  pthread_create (&thread_1,NULL,thread_f,NULL);
 
  pthread_join(thread_1, NULL);

  return 0;
}

void* thread_f(void* n)
{
  for (int a=5;a!=0; a--)
    {
    printf("Seconds to end of thread: %i\n",a);
    sleep(1);
    }
  printf("End of thread \n");
  pthread_exit(n);
}

The following image shows the compilation and execution of this code:

As we saw in the figure, the “for” loop completed its 5 cycles and the program is closed after thread_1 finished its execution. Let us see what happens if we remove the pthread_join() function. Compile and run the following code:

As we can see in the figure, the “for” loop of thread_1 could not complete one pass before the main() function is fully executed and the program is terminated.

How to Use the Pthread_Join() Function to Wait for a Multiple Thread to Complete the Execution in the C Language

In this example, we will show you how to use the pthread_join() function to wait for multiple concurrent threads to complete.

To do this, we use the code from the previous example and add a second thread which is thread_2 and a second routine which is thread_f_2() that executes in parallel with thread_1. The execution time of this routine is twice as long as that of thread_f_1() which takes 10 seconds.

The method that we use is to create the two threads one after the other using the pthread_create() function. Then, we first call the pthread_join() function with the shortest running thread, followed by a new call to this function with the longest running thread. The following is the complete code for this example:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void* thread_f_1(void* n);
void* thread_f_2(void* n);

int main()
{
  pthread_t thread_1;
  pthread_t thread_2;
  pthread_create (&thread_1,NULL,thread_f_1,NULL);
  pthread_create (&thread_2,NULL,thread_f_2,NULL);
 
  pthread_join(thread_1, NULL);
  pthread_join(thread_2, NULL);
  return 0;
}

void* thread_f_1(void* n)
{
  for (int a=5;a!=0; a--)
    {
    printf("Seconds to end of thread 1: %i\n",a);
    sleep(1);
    }
  printf("End of thread 1\n");
  pthread_exit(n);
}

void* thread_f_2(void* n)
{
  for (int a=10;a!=0; a--)
    {
    printf("Seconds to end of thread 2: %i\n",a);
    sleep(1);
    }
  printf("End of thread 2\n");
  pthread_exit(n);
}

As we see in the following image, both threads completed their execution:

Errors that the Pthread_Join() Function Can Return: What Are They and How Can They be Detected?

When an error occurs, the pthread_join() function returns one of the following predefined codes:

ESRCH: The specified identifier is not associated with any thread.

EINVAL: The thread is not joinable or another thread is already waiting to join this one.

EDEADLK: A crash is detected.

These errors are predefined in the “errno.h” header and are returned as a result in the output integer of the pthread_join() function, not in the global variable errno.

The following is the code from the previous example where we add an “if” condition to determine if an error occurred. If so, the program enters a switch condition that identifies the error and displays a message in the command console with the specific description of that error.

We also include the “errno.h” header and declare the error variable that will be the “if” and “switch” conditions.

To generate the error, we refer to the thread_2 in the thread input argument of the pthread_join() function which is not created.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>

void* thread_f(void* n);

int main()
{ int error;
  pthread_t thread_1;
  pthread_t thread_2;
  pthread_create (&thread_1,NULL,thread_f,NULL);
 
  error = pthread_join(thread_2, NULL);
  if (error!=0){

    switch (error){
    case  ESRCH:
            printf("The identifier is not associated with any thread\n");
            break;
    case  EINVAL:
            printf("Thread not joinable or another thread is already waiting\n");
            break;
    case  EDEADLK:
            printf("A crash was detected.\n");
            break;
}}
  return 0;
}

void* thread_f(void* n)
{
  for (int a=5;a!=0; a--)
    {
    printf("Seconds to end of thread: %i\n",a);
    sleep(1);
    }
  printf("End of thread \n");
  pthread_exit(n);
}

The following image shows the compilation and execution of this code. See how the program enters the “if” statement and the ESRCH instance of the switch statement which indicates that the thread_2 identifier points to a non-existent thread. You can also see that the execution of thread_1 is incomplete.

Conclusion

In this Linux Hint article, we showed you how to implement the pthread_join() function from the thread library to ensure a complete execution of a sub-process.

We explained the syntax of this function and described each of its input arguments and how to obtain the data that must be passed in them. We then implemented the function in a practical example with code snippets and images to show how you can use the pthread_join() function to ensure a complete and reliable execution of a thread.

We also showed you how to correctly link the thread library for error-free compilation. In a special section, we showed you how to detect and identify the errors that the pthread_join() function can generate.

About the author

Julio Cesar

Julio Cesar is a 42 years old programmer with 8 years of experience in embedded systems development, 6 years developing firmware for user interfaces in C and C++. Additionally he has 2 years of experience developing scripts for network devices and 3 years as developer of high frequency PCB (Printed Circuit Board).