When to use pthread_exit() and when to use pthread_join() in Linux?

Learn when to use pthread_exit() and when to use pthread_join() in linux? with practical examples, diagrams, and best practices. Covers c, linux, pthreads development techniques with visual explana...

Mastering Thread Synchronization: When to Use pthread_exit() vs. pthread_join()

Diagram illustrating thread creation, execution, and termination with join and exit points

Understand the critical differences and appropriate use cases for pthread_exit() and pthread_join() in Linux C programming to effectively manage thread lifecycles and resources.

In multi-threaded C applications on Linux, managing the lifecycle of threads is crucial for program correctness and resource efficiency. Two fundamental functions from the POSIX threads (pthreads) library, pthread_exit() and pthread_join(), play distinct roles in this management. While both relate to thread termination, their purposes, effects, and appropriate usage scenarios differ significantly. This article will clarify these differences, providing guidance on when and how to use each function effectively.

Understanding pthread_exit()

pthread_exit() is used by a thread to explicitly terminate itself. When a thread calls pthread_exit(), its execution stops, and a return value can be passed to any thread that might later join() with it. This function is analogous to exit() for a process, but it only terminates the calling thread, not the entire process. Resources local to the thread (like its stack) are deallocated, but process-wide resources remain intact. If the main thread calls pthread_exit(), the entire process will terminate only if it's the last thread running.

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

void *thread_function(void *arg) {
    printf("Thread %ld: Starting execution.\n", (long)pthread_self());
    // Do some work
    printf("Thread %ld: Exiting with status 100.\n", (long)pthread_self());
    pthread_exit((void*)100);
    // Code after pthread_exit() will not be executed
    return NULL; // This line is unreachable
}

int main() {
    pthread_t tid;
    int *thread_ret_val;

    printf("Main thread: Creating a new thread.\n");
    pthread_create(&tid, NULL, thread_function, NULL);

    printf("Main thread: Waiting for the new thread to exit.\n");
    pthread_join(tid, (void**)&thread_ret_val);

    printf("Main thread: Thread %ld exited with status %d.\n", (long)tid, (int)(long)thread_ret_val);

    return 0;
}

Example of a thread explicitly exiting using pthread_exit() and the main thread joining it.

Understanding pthread_join()

pthread_join() is used by one thread (the 'joining' thread) to wait for another thread (the 'joined' thread) to terminate. When pthread_join() is called, the calling thread blocks until the target thread finishes execution. This function is essential for ensuring that resources allocated by the joined thread are properly cleaned up and for retrieving its return value. If a thread is created as 'joinable' (the default), it must eventually be joined by another thread, or its resources will not be fully released, leading to a 'zombie thread' state similar to zombie processes.

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

void *worker_thread(void *arg) {
    printf("Worker thread %ld: Doing some heavy computation...\n", (long)pthread_self());
    sleep(2); // Simulate work
    printf("Worker thread %ld: Computation complete.\n", (long)pthread_self());
    return (void*)200; // Implicitly exits with status 200
}

int main() {
    pthread_t tid;
    void *thread_result;

    printf("Main thread: Creating worker thread.\n");
    pthread_create(&tid, NULL, worker_thread, NULL);

    printf("Main thread: Continuing its own work while worker runs.\n");
    sleep(1); // Main thread does some work

    printf("Main thread: Waiting for worker thread to finish.\n");
    pthread_join(tid, &thread_result);

    printf("Main thread: Worker thread %ld finished with result %d.\n", (long)tid, (int)(long)thread_result);

    return 0;
}

Example of pthread_join() used to wait for a worker thread and retrieve its return value.

When to Use Which?

The choice between pthread_exit() and pthread_join() depends entirely on the desired thread management strategy:

  • Use pthread_exit() (or return from start routine) when:

    • A thread needs to terminate itself explicitly before the end of its start routine.
    • You want to provide a specific exit status to a joining thread.
    • The thread has completed its task and no longer needs to execute.
  • Use pthread_join() when:

    • The main thread (or another thread) needs to wait for a specific child thread to complete its execution.
    • You need to retrieve the return value or exit status of a terminated thread.
    • You need to ensure that a joinable thread's resources are properly cleaned up to prevent resource leaks.
    • You need to synchronize the termination of threads, ensuring certain tasks are finished before proceeding.
flowchart TD
    A[Thread Creation] --> B{Is Thread Joinable?}
    B -->|Yes| C[Thread Executes]
    C --> D{Thread Terminates?}
    D -->|Yes (pthread_exit() or return)| E[Thread Becomes Zombie]
    E --> F[pthread_join() Called]
    F --> G[Resources Reclaimed]
    G --> H[Joining Thread Continues]
    B -->|No (pthread_detach())| I[Thread Executes]
    I --> J{Thread Terminates?}
    J -->|Yes (pthread_exit() or return)| K[Resources Immediately Reclaimed]
    K --> L[No Join Needed]
    H --> M[Program Continues]
    L --> M

Flowchart illustrating thread lifecycle with pthread_exit() and pthread_join() or pthread_detach().