Creating threads with pthread_create
Start, identify, and join POSIX threads in C using pthread_create, pthread_join, and pthread_self.
- Create a new thread with pthread_create
- Write a thread function with the correct signature
- Wait for a thread to finish with pthread_join
- Pass data to a thread through its argument pointer
The pthread_create function starts a new thread. Like a function call, it takes a function pointer and an argument. Unlike a function call, it returns immediately — the new thread runs concurrently with the calling thread.
pthread_create signature
int pthread_create(
pthread_t *thread, /* receives the new thread's ID */
const pthread_attr_t *attr, /* NULL for default attributes */
void *(*start_routine)(void *), /* the thread function */
void *arg /* argument passed to start_routine */
);Returns 0 on success, an error code on failure. The pthread_t is an opaque handle for the new thread.
A minimal example
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *greet(void *arg) {
char *name = (char *)arg;
printf("Hello from thread: %s\n", name);
return NULL;
}
int main(void) {
pthread_t t1, t2;
pthread_create(&t1, NULL, greet, "Alice");
pthread_create(&t2, NULL, greet, "Bob");
pthread_join(t1, NULL); /* wait for t1 to finish */
pthread_join(t2, NULL); /* wait for t2 to finish */
printf("Both threads done\n");
return 0;
}Compile: gcc -Wall -pthread threads.c -o threads
The output order of the two greeting lines is not deterministic — either thread may run first.
pthread_join
pthread_join(thread, retval) blocks until the specified thread exits:
void *result;
pthread_join(t1, &result); /* result receives the return value of greet */If the thread function returns a value, you can collect it through the second argument:
void *compute_sum(void *arg) {
int n = *(int *)arg;
long *result = malloc(sizeof(long));
*result = (long)n * (n + 1) / 2; /* sum 1..n */
return result; /* caller must free */
}
/* In main: */
int n = 1000;
pthread_t t;
pthread_create(&t, NULL, compute_sum, &n);
long *result;
pthread_join(t, (void **)&result);
printf("Sum 1..%d = %ld\n", n, *result);
free(result);A detached thread (created with pthread_attr_setdetachstate or via pthread_detach) cannot be joined — it cleans itself up when done.
Passing data to threads
The arg parameter is a void * — you can pass any pointer. Common patterns:
Single value (taking its address):
int n = 42;
pthread_create(&t, NULL, worker, &n); /* pass &n; n must outlive the thread */Multiple values (a struct):
typedef struct {
int id;
double *data;
int len;
} WorkerArgs;
WorkerArgs args = { .id = 1, .data = array, .len = n };
pthread_create(&t, NULL, worker, &args);Do not pass a pointer to a local variable that may go out of scope before the thread uses it. A classic bug: creating a thread in a loop and passing a pointer to the loop variable. By the time the thread runs, the variable may have changed. Use per-thread heap allocations or ensure the main thread waits for the child before modifying the argument.
Threads vs. processes
Understand the difference between processes and threads in C — memory isolation, the cost of context switching, and when to use each.
Mutexes and race conditions
Protect shared data in C with POSIX mutexes — initialising, locking, and unlocking pthread_mutex_t to eliminate data races.