Beginner
pthread_create()
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.
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()
clone() system call (Linux-specific). The new thread gets its own stack and a copy of the CPU registers.thread. Use this ID in future calls like pthread_join().start(arg). The new thread calls the function pointed to by start, passing arg to it.pthread_create() continues with the very next line of code. Both threads now run concurrently.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 asarg— 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 callingpthread_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 tovoid *. 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:
Cast the integer to void * directly.
pthread_create(&tid, NULL, fn, (void *)(intptr_t) 42);
Pass a pointer to a global or heap variable.
int val = 5; pthread_create(&tid, NULL, fn, &val);
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
void *start_function(void *arg); — it takes one void * argument and returns void *. The void * types allow passing and returning any data type.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.malloc().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().void * instead.How threads end: return, pthread_exit(), pthread_cancel(), and what happens when exit() is called from any thread.
