Thread Creation — pthread_create()

Thread Creation — pthread_create()
Part 3 of 9  |  Creating new threads · Start functions · Arguments
Level
Beginner
Function
pthread_create()
Book
TLPI – Ch 29.3

Creating a thread is the most fundamental operation in multithreaded programming. In Pthreads, you use the function pthread_create() to spawn a new thread. This part explains the function signature in detail, what each argument means, how the start function works, and what happens immediately after the call.

Key Terms

pthread_create() Start Function void * argument Thread Attributes clone() system call Race Condition Scheduling PTHREAD_CANCELED

1. pthread_create() — Function Signature

#include <pthread.h>

int pthread_create(pthread_t        *thread,
                   const pthread_attr_t *attr,
                   void *(*start)(void *),
                   void             *arg);

/* Returns: 0 on success, positive error number on failure */

This function has four parameters. Let’s understand each one carefully:

Parameter Type Meaning
thread pthread_t * Output. The unique ID of the newly created thread is written here. Pass the address of a pthread_t variable.
attr const pthread_attr_t * Thread attributes (stack size, scheduling, detach state…). Pass NULL for all defaults — this is sufficient for most programs.
start void *(*)(void *) The function the new thread will run. It must match the signature void *func(void *) — takes one void * argument and returns void *.
arg void * Argument passed to the start function. Can be a pointer to any type, or NULL. To pass multiple values, point to a struct.

2. What Happens When You Call pthread_create()

1
New thread is created. The OS kernel is invoked via the clone() system call (Linux-specific). The new thread gets its own stack and a copy of the CPU registers.
2
Thread ID is written. Before returning, the unique thread ID of the new thread is stored into the buffer pointed to by thread. Use this ID in future calls like pthread_join().
3
New thread begins executing start(arg). The new thread calls the function pointed to by start, passing arg to it.
4
Caller continues immediately. The thread that called pthread_create() continues with the very next line of code. Both threads now run concurrently.
No guaranteed scheduling order. After pthread_create() returns, there is no guarantee about which thread runs next — the old caller or the new thread. Never write code that assumes one runs before the other without explicit synchronisation.

3. The Start Function — Prototype and Rules

Every thread you create must have a start function with exactly this signature:

void *my_thread_function(void *arg);

Rules for the start function:

  • Takes void *: You can pass any type of data as arg — a primitive cast to pointer, or a pointer to a struct. Cast it back inside the function.
  • Returns void *: This return value can be retrieved by another thread calling pthread_join(). You can return status codes, pointers to results, etc.
  • Stack safety: Do NOT return a pointer to a local variable — the stack frame is destroyed when the function returns and the thread terminates. Return heap-allocated data or a global, or a simple integer cast.
  • Avoid PTHREAD_CANCELED: That special cancellation value is usually an integer cast to void *. If your thread returns the same integer value, a joining thread may confuse it for cancellation.

4. Passing Arguments to a Thread

The arg parameter is a single void *. You can use it in three common ways:

1. Single integer

Cast the integer to void * directly.

pthread_create(&tid, NULL, fn,
  (void *)(intptr_t) 42);
2. Single pointer

Pass a pointer to a global or heap variable.

int val = 5;
pthread_create(&tid, NULL, fn, &val);
3. Struct pointer (multiple args)

Pack multiple values into a struct.

struct Args { int x; char *s; };
struct Args *a = malloc(sizeof *a);
pthread_create(&tid,NULL,fn,a);

5. Code Example: Hello World with pthread_create()

This is the classic “Hello World” equivalent for threads — the program from TLPI Listing 29-1.

/* compile: gcc -pthread -o simple_thread simple_thread.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

/* ---- start function for the new thread ---- */
static void *threadFunc(void *arg)
{
    char *s = (char *) arg;   /* cast void* back to char* */
    printf("%s", s);

    /* Return length of string as exit value (cast int → void*) */
    return (void *)(intptr_t) strlen(s);
}

int main(void)
{
    pthread_t t1;
    void      *res;   /* will hold thread's return value */
    int        s;

    /* Create thread — pass string as argument */
    s = pthread_create(&t1, NULL, threadFunc, "Hello world\n");
    if (s != 0) {
        fprintf(stderr, "pthread_create: %s\n", strerror(s));
        exit(EXIT_FAILURE);
    }

    printf("Message from main()\n");

    /* Wait for thread to finish and retrieve its return value */
    s = pthread_join(t1, &res);
    if (s != 0) {
        fprintf(stderr, "pthread_join: %s\n", strerror(s));
        exit(EXIT_FAILURE);
    }

    printf("Thread returned %ld\n", (long)(intptr_t) res);
    exit(EXIT_SUCCESS);
}

Sample output (order of first two lines may vary):

Message from main()
Hello world
Thread returned 12

The output order can vary because after pthread_create() returns, both the main thread and the new thread are runnable. The scheduler decides which prints first.

6. Code Example: Passing Multiple Arguments via Struct

When you need to pass more than one value to a thread, the cleanest approach is to allocate a struct on the heap and pass its address as arg.

/* compile: gcc -pthread -o thread_struct thread_struct.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

/* Struct to hold multiple arguments */
struct ThreadArgs {
    int   thread_num;
    char  message[64];
};

static void *worker(void *arg)
{
    struct ThreadArgs *targs = (struct ThreadArgs *) arg;

    printf("Thread %d says: %s\n", targs->thread_num, targs->message);

    free(targs);   /* free heap allocation (thread owns it now) */
    return NULL;
}

int main(void)
{
    pthread_t tid[3];
    int s;

    for (int i = 0; i < 3; i++) {
        struct ThreadArgs *a = malloc(sizeof(struct ThreadArgs));
        if (!a) { perror("malloc"); exit(1); }

        a->thread_num = i + 1;
        snprintf(a->message, sizeof(a->message),
                 "Hello from worker %d!", i + 1);

        s = pthread_create(&tid[i], NULL, worker, a);
        if (s != 0) {
            fprintf(stderr, "pthread_create: %s\n", strerror(s));
            exit(EXIT_FAILURE);
        }
    }

    /* Wait for all three threads */
    for (int i = 0; i < 3; i++)
        pthread_join(tid[i], NULL);

    printf("All threads done.\n");
    return 0;
}

Important: We allocate each thread’s argument struct with malloc() so every thread gets its own independent copy. If we used a single stack variable in a loop, all threads would read the same (possibly already-changed) memory. The thread itself calls free() after reading its arguments.

7. Interview Questions

Q1. What is the signature of the start function for a Pthreads thread?
void *start_function(void *arg); — it takes one void * argument and returns void *. The void * types allow passing and returning any data type.
Q2. What does the attr argument in pthread_create() do? What does NULL mean?
The attr argument points to a pthread_attr_t structure that controls thread properties like stack size, detach state, and scheduling policy. Passing NULL uses all default attributes, which is appropriate for most programs.
Q3. After pthread_create() returns, which thread runs next — the caller or the new thread?
There is no guarantee. Both threads are runnable and the OS scheduler decides which executes next. This depends on the system’s scheduling policy, current load, and other factors. You must use synchronisation primitives (mutexes, condition variables) if execution order matters.
Q4. Why is it dangerous to pass a pointer to a local variable as the thread argument in a loop?
If the creating thread modifies or re-uses the local variable before the new thread reads it, the thread reads incorrect (overwritten) data. The safest approach is to allocate separate heap memory for each thread’s arguments with malloc().
Q5. What Linux system call does pthread_create() use internally?
clone() with sharing flags that allow the child to share the parent’s virtual address space, file descriptors, and other resources. This is what makes thread creation faster than fork().
Q6. Can you return a pointer to a local variable from a thread’s start function?
No. When the start function returns, the thread terminates and its stack is freed (or may be reused by another thread). Any pointer into that stack becomes a dangling pointer. Return heap-allocated memory, a global, or a simple integer cast to void * instead.

Next: Part 4 — Thread Termination

How threads end: return, pthread_exit(), pthread_cancel(), and what happens when exit() is called from any thread.

Leave a Reply

Your email address will not be published. Required fields are marked *