Linux System Programming · intermediate · ~10 min

Passing arguments to threads

Pass per-thread data into `pthread_create` safely — without sharing a single loop variable across threads.

Lesson

A thread function always has this signature:

void *fn(void *arg);

It receives exactly one pointer. To send more than one value, pack the values into a struct and pass the struct's address.

A common, dangerous bug

The classic mistake is passing the address of a loop variable (the counter i):

/* WRONG — every thread reads the same `i` */
for (int i = 0; i < N; i++)
    pthread_create(&tids[i], NULL, worker, &i);

Here is the problem. The new threads do not run instantly. By the time they actually read i, the loop has usually finished and i == N. So every thread reads N, not the value you intended.

Two safe fixes

  • Pass by value. Cast the int into intptr_t, then into void *, and reverse the cast inside the thread. (intptr_t is an integer type guaranteed to be wide enough to hold a pointer.) No shared variable, no race.
  • Allocate per-thread data on the heap. Give each thread its own malloc'd struct so nothing is shared or reassigned.

Code examples

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

static void *worker(void *arg) {
    int my_id = (int)(intptr_t)arg;
    printf("thread %d alive\n", my_id);
    return NULL;
}

int main(void) {
    pthread_t tids[4];
    for (int i = 0; i < 4; i++) {
        /* cast int -> intptr_t -> void* keeps the value, not an address */
        pthread_create(&tids[i], NULL, worker, (void *)(intptr_t)i);
    }
    for (int i = 0; i < 4; i++) pthread_join(tids[i], NULL);
    return 0;
}

Common mistakes

  • Passing &i from a loop. All threads see the post-loop value of i, not their intended index.
  • Pointing at a stack-local struct that disappears too soon. If the function returns before the thread reads the struct, the data is gone. Use malloc'd arguments, or pass the value by value.

Summary

  • A thread function takes one void * argument; pack multiple values into a struct.
  • Never pass the address of a stack-local loop variable — the loop reassigns it before the thread reads.
  • Safe options: pass an int by value via intptr_t, or give each thread its own heap-allocated struct.

Practice with these exercises