Code of the Day
AdvancedConcurrency with pthreads

Creating threads with pthread_create

Start, identify, and join POSIX threads in C using pthread_create, pthread_join, and pthread_self.

CAdvanced11 min read
Recommended first
By the end of this lesson you will be able to:
  • 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.

Finished reading? Mark it complete to track your progress.

On this page