Beginner
pthread_exit()
TLPI – Ch 29.4
Every thread that starts must eventually finish. Understanding the different ways a thread can terminate is critical — some methods affect only the calling thread, while others terminate the entire process instantly. Choosing the wrong termination method is a common source of subtle bugs in multithreaded programs.
1. The Four Ways a Thread Can Terminate
A thread’s execution ends in exactly one of four ways:
The cleanest way. When the start function executes a return statement, the thread terminates normally. The return value becomes the thread’s “exit status”, retrievable via pthread_join(). This is exactly like main() returning in a single-threaded program.
The thread calls pthread_exit(retval) explicitly. This terminates only the calling thread. Other threads in the process continue running. It is useful when you want to exit the thread from a deeply nested helper function, not just from the start function itself. Think of it as the thread equivalent of calling exit() for a process.
Another thread calls pthread_cancel(tid) to cancel this thread. The thread does not stop immediately; cancellation happens at a cancellation point (e.g., inside blocking calls like read(), sleep(), etc.). Cancellation is covered in detail in Chapter 32.
If any thread calls exit(), or if the main thread executes a return in main(), all threads in the process are terminated immediately. This is often surprising for beginners — even detached threads are killed.
2. pthread_exit() — Detailed Explanation
#include <pthread.h>
void pthread_exit(void *retval);
/* Never returns */
pthread_exit() terminates the calling thread. The retval argument specifies the thread’s “return value”, which another thread can collect using pthread_join().
The retval you pass to pthread_exit() must not point to anything on the thread’s stack. When the thread terminates, its stack is freed and may be immediately reused by a new thread. Any pointer into that stack becomes a dangling pointer.
Safe options for retval:
- A simple integer cast:
(void *)(intptr_t) status_code - A pointer to a heap-allocated structure
- A pointer to a global variable
Special case — main thread: If the main thread calls pthread_exit() instead of return or exit(), the main thread terminates but the other threads in the process continue executing. The process stays alive until all threads have finished. This is a very useful pattern when you want to fire off worker threads and not block the main thread waiting for them.
3. Comparison: Termination Methods
| Method | Who Terminates? | Other Threads? | Return Value? |
|---|---|---|---|
| return | Calling thread only | Continue | Yes (return value) |
| pthread_exit() | Calling thread only | Continue | Yes (retval arg) |
| pthread_cancel() | Target thread (at cancellation point) | Continue | PTHREAD_CANCELED |
| exit() / main() return | Entire process | All killed immediately | No (process ends) |
4. Code Example: pthread_exit() vs return
This example demonstrates the difference between using return and pthread_exit(), and shows that calling pthread_exit() from main() lets other threads finish.
/* compile: gcc -pthread -o thread_exit thread_exit.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static void *slow_worker(void *arg)
{
int id = (int)(intptr_t) arg;
printf("Worker %d: started\n", id);
sleep(2); /* simulate work */
printf("Worker %d: finished\n", id);
return (void *)(intptr_t) id; /* return thread number as exit code */
}
int main(void)
{
pthread_t t1, t2;
pthread_create(&t1, NULL, slow_worker, (void *)(intptr_t) 1);
pthread_create(&t2, NULL, slow_worker, (void *)(intptr_t) 2);
printf("Main: threads created, main will now exit via pthread_exit()\n");
printf("Main: workers will still finish!\n");
/*
* pthread_exit() in main — does NOT kill the workers.
* The process stays alive until t1 and t2 both finish.
*
* If we used exit(0) or return 0 here, workers would be killed!
*/
pthread_exit(NULL);
/* Code below is never reached */
printf("This line is never printed.\n");
return 0;
}
Expected output:
Main: threads created, main will now exit via pthread_exit()
Main: workers will still finish!
Worker 1: started
Worker 2: started
Worker 1: finished
Worker 2: finished
Notice: the main thread exits early but the workers complete. If you change pthread_exit(NULL) to exit(0) or return 0, the workers would be killed before they print “finished”.
5. Code Example: Stack Pointer Danger
This example illustrates what NOT to do — returning a pointer to a local (stack) variable. The code compiles but has undefined behaviour.
/* compile: gcc -pthread -o stack_danger stack_danger.c
WARNING: This demonstrates a BUG — undefined behaviour */
#include <stdio.h>
#include <pthread.h>
/* WRONG — returns pointer to local variable */
static void *bad_thread(void *arg)
{
int local_result = 42; /* on the STACK */
/* DANGEROUS: stack is freed when thread exits! */
return &local_result; /* BUG: dangling pointer */
}
/* RIGHT — returns heap-allocated value */
static void *good_thread(void *arg)
{
int *result = malloc(sizeof(int)); /* on the HEAP — safe */
*result = 42;
return result; /* caller must free() this */
}
int main(void)
{
pthread_t t;
void *res;
/* WRONG way */
pthread_create(&t, NULL, bad_thread, NULL);
pthread_join(t, &res);
/* res now points to freed stack memory — undefined behaviour */
printf("bad_thread result (UNDEFINED): %d\n", *(int *)res);
/* RIGHT way */
pthread_create(&t, NULL, good_thread, NULL);
pthread_join(t, &res);
printf("good_thread result: %d\n", *(int *)res);
free(res); /* we own the heap allocation now */
return 0;
}
The bad_thread example may appear to work sometimes (by luck), but it is a bug. Always use heap allocation or a simple integer cast for thread return values.
6. Interview Questions
return statement; (2) the thread calls pthread_exit(); (3) the thread is cancelled via pthread_cancel(); (4) any thread in the process calls exit(), or the main thread returns from main(), which terminates all threads.exit() terminates the entire process — all threads are killed immediately. pthread_exit() terminates only the calling thread; other threads continue running normally.void * instead.pthread_exit() is useful when you want to terminate the thread from a function that is called by the start function (not from the start function directly). With a simple return, you can only return from the immediate function — you would have to propagate the return through every level of the call stack manually. pthread_exit() exits immediately from wherever it is called.Learn how threads are identified: pthread_self(), pthread_equal(), and the difference between POSIX thread IDs and kernel thread IDs.
