1 - Thread Lifecycle and Joinable vs Detached Threads
Practice Program
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* worker1()
{
printf("Thread1 worker\n");
sleep(2);
return NULL;
}
void* worker2(void* arg) {
char* str = (char*)arg;
while (1) {
printf("%s\n", str);
sleep(1);
}
}
/**
* Here, we have not specified whether a thread is joinable or detachable. It is recommended to explicitly define
* a thread as joinable or detachable based on the expected behaviour as some implementations might have a different
* default.
*
* A joinable thread can be made detachable through `pthread_detachable` function.
*/
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1, NULL, worker1, NULL);
/**
* `pthread_join` function blocks the main thread until the called thread terminates or calls pthread_exit
* This function frees up the resources used by the thread. If this function is not called for joinable thread,
* the thread after completing it's work becomes a zombie thread. The difference between joinable and detachable
* thread is that detachable thread automatically frees up the resource after completing it's work whereas a
* joinable thread doesn't
*/
pthread_join(tid1, NULL);
static char* string = "Thread2 worker";
pthread_create(&tid2, NULL, worker2, (void*)string);
/**
* `pthread_join` is never called for thread 2 and it becomes a zombie thread. It's resources are deallocated when
* the main thread terminates.
*/
printf("Main thread\n");
sleep(10);
return 0;
}1. Creating Threads
Threads in the pthread library are created using:
pthread_create(&thread_id, attr, start_routine, arg);Parameters
- thread_id
Pointer topthread_twhere the thread identifier will be stored. - attr
Thread attributes.
NULLmeans default attributes are used. - start_routine
Function executed by the thread. - arg
Argument passed to the thread function.
Example
pthread_create(&tid1, NULL, worker1, NULL);This creates a thread that runs:
void* worker1()
{
printf("Thread1 worker\n");
sleep(2);
return NULL;
}Execution flow:
- Thread prints a message
- Sleeps for 2 seconds
- Returns, which terminates the thread
2. Thread Function Signature
All pthread thread functions must follow this format:
void* function(void* arg)Reason:
- Allows passing any data type through a
void* - Allows returning any pointer type
Example:
void* worker2(void* arg) {
char* str = (char*)arg;
while (1) {
printf("%s\n", str);
sleep(1);
}
}Here:
argis passed asvoid*- It is cast to
char* - The thread repeatedly prints the message
3. Thread Termination
A thread can terminate in several ways:
- Returning from the thread function
return NULL;- Calling
pthread_exit(NULL);- Being cancelled by another thread
- Process termination
4. Joinable Threads
By default, a thread is typically joinable, although POSIX recommends explicitly specifying this behavior because different implementations may have different defaults.
A joinable thread:
- Must be explicitly waited on
- Keeps its resources allocated after finishing execution
- Requires another thread to call
pthread_join()
Joining a thread
pthread_join(tid1, NULL);This does two things:
- Blocks the calling thread until
tid1finishes. - Releases system resources used by the thread.
Example Flow
Main thread
|
|--- create tid1
|
|--- pthread_join(tid1)
(main thread waits here)
|
|--- continue executionIn the example program:
pthread_create(&tid1, NULL, worker1, NULL);
pthread_join(tid1, NULL);The main thread blocks until worker1 finishes.
5. Zombie Threads
If a joinable thread finishes execution but pthread_join() is never called, it becomes a zombie thread.
A zombie thread:
- Has finished execution
- Still holds system resources
- Remains until
pthread_join()is called
This is similar in concept to zombie processes.
6. Detached Threads
A detached thread automatically releases its resources when it finishes execution. No other thread needs to call pthread_join(). A thread can be made detached using:
pthread_detach(thread_id);Or via thread attributes during creation.
7. Example from the Program
After the first thread completes, the program creates a second thread:
static char* string = "Thread2 worker";
pthread_create(&tid2, NULL, worker2, (void*)string);This thread:
- Prints
"Thread2 worker"every second - Runs an infinite loop
Important point:
pthread_join(tid2, NULL);is never called.
Therefore:
tid2remains joinable- If it terminates, it would become a zombie thread
However, in this program:
- The thread never exits because of the infinite loop.
8. Resource Cleanup at Process Termination
Even if a thread becomes a zombie, its resources are eventually reclaimed when the process exits.
In this example:
sleep(10);
return 0;After 10 seconds:
- The main thread exits
- The entire process terminates
- All threads and resources are reclaimed by the OS.
9. Program Execution Timeline
Possible output sequence:
Thread1 worker
Main thread
Thread2 worker
Thread2 worker
Thread2 worker
...Execution order:
tid1starts- Main thread waits using
pthread_join worker1completestid2starts- Main thread prints
"Main thread" worker2runs repeatedly- Process exits after 10 seconds
10. Best Practice
It is recommended to explicitly define thread detach state rather than relying on defaults.
Two common patterns:
Joinable worker threads
Use when the result or completion matters.
create → joinDetached background threads
Use when the thread is independent.
create → detach → forget