Linux System Programming · intermediate · ~10 min

Writing thread-safe code

Recognise what makes a function thread-safe and what doesn't.

Lesson

A thread-safe function can be called from multiple threads concurrently and still produce the documented result. Three rough categories:

  1. Reentrant — uses only its arguments and locals; no shared state at all. E.g. strlen(const char *s). Automatically thread-safe.
  2. Thread-safe with internal locking — has shared state but takes a lock internally. E.g. malloc on glibc.
  3. Not thread-safe — uses static state that the caller must serialize. Classic examples: strtok, gmtime, asctime, rand. Each has a reentrant _r variant: strtok_r, gmtime_r, rand_r.

When you write your own helpers:

  • Prefer pure functions (no globals, no static buffers).
  • If you need state, put it in a struct the caller owns and document that they must serialize it.
  • If you must use globals, wrap them in a mutex or use _Atomic.

Code examples

/* Not thread-safe — internal static buffer */
char *to_hex(unsigned x) {
    static char buf[16];
    snprintf(buf, sizeof buf, "%x", x);
    return buf;       /* every caller shares this buffer */
}

/* Thread-safe — caller provides the buffer */
char *to_hex_r(unsigned x, char *buf, size_t cap) {
    snprintf(buf, cap, "%x", x);
    return buf;
}

Common mistakes

  • Using strtok, gmtime, asctime, ctime in multithreaded code. Use the _r variants.
  • Believing that "global counters are fine if they're only int." Read-modify-write is never atomic on an int without _Atomic.

Summary

Thread-safe = no unprotected shared state. Avoid strtok/gmtime/asctime/rand; use *_r variants. Prefer pure functions.

Practice with these exercises