Beginner
Pthreads Foundations
TLPI – Ch 29.2
Before writing any Pthreads code, you need to understand a few fundamental concepts that apply to every Pthreads function: the special data types the API uses, how errno works differently in threads, how to interpret return values, and what compiler flags are required. Get these basics right and the rest of the API will make perfect sense.
1. Pthreads Data Types
The Pthreads API defines its own set of data types. You will use these types to declare threads, mutexes, condition variables, and other objects. The table below lists the most important ones.
| Data Type | Description | Used With |
|---|---|---|
| pthread_t | Thread identifier | pthread_create(), pthread_join() |
| pthread_mutex_t | Mutex (mutual exclusion lock) | pthread_mutex_lock(), pthread_mutex_unlock() |
| pthread_mutexattr_t | Mutex attributes object | Customising mutex behaviour |
| pthread_cond_t | Condition variable | pthread_cond_wait(), pthread_cond_signal() |
| pthread_condattr_t | Condition variable attributes | Customising condition variables |
| pthread_key_t | Key for thread-specific data | pthread_key_create(), pthread_setspecific() |
| pthread_once_t | One-time initialisation control | pthread_once() |
| pthread_attr_t | Thread attributes object | pthread_create() |
All these types are opaque. This means you must never look inside their structure or compare them using the == operator. The internal layout is implementation-defined and can differ between systems. Always use the Pthreads API functions to work with them.
pthread_t t1, t2;
/* WRONG: if (t1 == t2) … */
/* RIGHT: if (pthread_equal(t1, t2)) … */
2. The errno Variable in Multithreaded Programs
In traditional (single-threaded) C programs, errno is a global integer that system calls set when they fail. In a multithreaded program this approach would be broken: if thread A sets errno to ENOMEM and then the OS switches to thread B before thread A reads errno, thread B might overwrite errno with a different error. Thread A would then read the wrong error code.
The solution is thread-local errno. In modern POSIX implementations, errno is implemented as a macro that expands to a function call returning a per-thread storage location. Including <errno.h> is mandatory — you cannot use the old extern int errno declaration.
The beauty of this design: from the programmer’s perspective, errno looks and works exactly like before — you can still write errno = 0; and if (errno == EINVAL). The thread-safety is transparent.
3. Return Values from Pthreads Functions
Pthreads functions follow a different convention from regular UNIX system calls. This is a very common source of confusion for beginners.
- Return
-1on failure - Set
errnoto the error code - Return
0or positive value on success
if (fd == -1) { /* use errno */ }
- Return
0on success - Return a positive error number on failure
- Never return
-1 - Do NOT set
errnodirectly
if (s != 0) { /* s is the error code */ }
The recommended pattern (from TLPI) is to store the return value in an intermediate variable s, then pass it to an error-exit helper like errExitEN(s, "pthread_create"). Avoid assigning directly to errno because accessing errno in a threaded program has a small function-call overhead.
4. Compiling Pthreads Programs
On Linux, any program that uses the Pthreads API must be compiled with the -pthread flag:
gcc -pthread -o my_program my_program.c
The -pthread flag does two things:
_REENTRANT
Enables declarations of reentrant (thread-safe) versions of library functions. For example, it ensures thread-safe versions of functions like strtok_r() are visible.
libpthread
Equivalent to adding -lpthread. The Pthreads library contains the implementation of all pthread_*() functions. Without this, you will get linker errors.
-lpthread alone. This works for linking but does not define _REENTRANT. Always prefer -pthread. On Solaris use -mt; on HP-UX also -mt.5. Code Example: Proper Error Handling in Pthreads
The following shows the standard error-checking pattern recommended for all Pthreads function calls. This pattern will appear throughout every Pthreads program you write.
/* compile: gcc -pthread -o pth_errcheck pth_errcheck.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
/* Simple helper — prints error and exits */
static void handle_error(int err, const char *msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(err));
exit(EXIT_FAILURE);
}
static void *thread_fn(void *arg)
{
printf("Hello from thread!\n");
return NULL;
}
int main(void)
{
pthread_t tid;
int s; /* always capture return value in 's' */
/* pthread_create returns 0 on success, positive errno on failure */
s = pthread_create(&tid, NULL, thread_fn, NULL);
if (s != 0)
handle_error(s, "pthread_create");
s = pthread_join(tid, NULL);
if (s != 0)
handle_error(s, "pthread_join");
printf("Thread finished.\n");
return 0;
}
Notice: we check s != 0, not s == -1. The return value s itself is the error code (like ENOMEM, EAGAIN) — it is passed directly to strerror().
6. Code Example: Per-Thread errno
This example demonstrates that each thread has its own independent errno value by deliberately causing an error in one thread and checking that it does not affect the other.
/* compile: gcc -pthread -o thread_errno thread_errno.c */
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
static void *bad_open_fn(void *arg)
{
/* Try to open a non-existent file — sets errno in THIS thread */
int fd = open("/no/such/file", O_RDONLY);
if (fd == -1)
printf("Thread: open() failed, errno = %d (%s)\n",
errno, strerror(errno)); /* errno is thread-local */
/* Reset errno to 0 in this thread */
errno = 0;
printf("Thread: errno after reset = %d\n", errno);
return NULL;
}
int main(void)
{
pthread_t tid;
/* errno starts at 0 in main thread */
errno = 0;
pthread_create(&tid, NULL, bad_open_fn, NULL);
pthread_join(tid, NULL);
/* Main thread's errno should still be 0 — not affected by child thread */
printf("Main: errno = %d (should be 0)\n", errno);
return 0;
}
Key point: The child thread’s open() failure sets errno only in that thread’s private copy. The main thread’s errno remains 0.
7. Interview Questions
_REENTRANT preprocessor macro (enabling reentrant function declarations) and links the program with libpthread, which contains all the Pthreads API implementations.errno. Traditional UNIX system calls return -1 on failure and set errno.errno, a system call failure in thread A could be overwritten by a different error in thread B before thread A reads the value. Per-thread errno eliminates this race condition. In modern implementations, errno is a macro that returns a thread-local storage location.pthread_t, pthread_mutex_t) so that the implementation can change internally without breaking application code. You must use provided API functions to manipulate them — comparing them with == is not portable.Learn the full signature of pthread_create(), understand the start function prototype, and write your first real multithreaded program.
