Pthreads API — Background Details

Pthreads API — Background Details
Part 2 of 9  |  Data Types · errno · Return Values · Compilation
Level
Beginner
Topic
Pthreads Foundations
Book
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.

Key Terms

pthread_t pthread_mutex_t errno (thread-local) Opaque Data Type Return Value Convention -pthread flag errExitEN() POSIX.1c

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()
⚠️ Important — Opaque Data Types

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.

Thread 1
errno = ENOMEM
Own private copy
Thread 2
errno = EINVAL
Own private copy
Thread 3
errno = 0 (no error)
Own private copy

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.

❌ Traditional UNIX System Calls

  • Return -1 on failure
  • Set errno to the error code
  • Return 0 or positive value on success
fd = open(“file”, O_RDONLY);
if (fd == -1) { /* use errno */ }
✅ Pthreads Functions

  • Return 0 on success
  • Return a positive error number on failure
  • Never return -1
  • Do NOT set errno directly
s = pthread_create(…);
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:

1. Defines _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.

2. Links libpthread

Equivalent to adding -lpthread. The Pthreads library contains the implementation of all pthread_*() functions. Without this, you will get linker errors.

Note: Some older guides say use -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

Q1. What does the -pthread compiler flag do on Linux?
It defines the _REENTRANT preprocessor macro (enabling reentrant function declarations) and links the program with libpthread, which contains all the Pthreads API implementations.
Q2. How do Pthreads functions report errors? How is this different from system calls?
Pthreads functions return 0 on success and a positive error number on failure. They do not return -1 and do not set errno. Traditional UNIX system calls return -1 on failure and set errno.
Q3. Why does each thread need its own errno?
If all threads shared one global 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.
Q4. What is an opaque data type? Why does Pthreads use them?
An opaque data type is one whose internal structure is hidden from the programmer. Pthreads uses opaque types (like 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.
Q5. When was the POSIX threads standard finalised and what is it part of?
POSIX threads (defined in POSIX.1c) were standardised in 1995 and later incorporated into SUSv3 (Single UNIX Specification version 3). Before this, several incompatible threading APIs existed across different UNIX vendors.

Next: Part 3 — Thread Creation with pthread_create()

Learn the full signature of pthread_create(), understand the start function prototype, and write your first real multithreaded program.

Leave a Reply

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