A function is thread-safe if it works correctly when called simultaneously by multiple threads. A function is reentrant if it can be interrupted (e.g., by a signal handler) and called again before the first call completes.
Thread Safety Levels
| Level | Description | Example |
|---|---|---|
| Unsafe | Uses shared state without synchronization | A global counter without a lock |
| Conditionally safe | Safe only if called in specific patterns | strtok() — unsafe, uses internal state |
| Thread-safe | Uses synchronization (mutex, atomic) to protect shared state | pthread_mutex_lock around critical section |
| Reentrant | No shared state at all — pure function or thread-local state only | strtok_r() (reentrant version) |
Reentrancy
A function is reentrant if:
- It uses only local variables (no static/global state)
- It does not modify its own code (self-modifying code)
- It does not call non-reentrant functions
// NOT reentrant — uses global state
int counter = 0;
int next_id() {
return counter++; // interrupted here → bad
}
// Reentrant — caller provides state
int next_id_r(int *counter) {
return (*counter)++;
}
Thread-Safe vs Reentrant
| Thread-safe | Reentrant | |
|---|---|---|
| Multiple threads | Yes | Yes |
| Interrupted and re-entered | Maybe | Yes |
| Uses locks | Maybe | Never |
| Uses shared state | Yes (with protection) | No |
A reentrant function is always thread-safe (no shared state to protect), but a thread-safe function is not necessarily reentrant (it might hold a mutex and deadlock if re-entered from a signal handler).
Making Code Thread-Safe
- Avoid global/static state — prefer parameters and return values
- Use mutexes to protect shared state
- Use atomic operations for simple counters/flags
- Use Thread-Local Storage (TLS) — each thread gets its own copy of a variable
// Thread-local: each thread has its own copy
__thread int thread_local_var;
// Now safe — no shared state
int get_id() {
static __thread int id = 0;
return id++;
}
Q: What is the difference between thread-safe and reentrant?
A: Thread-safe means the function works correctly with multiple concurrent calls using synchronization. Reentrant means the function can be interrupted and called again before the first invocation finishes — it must use no shared state and no locks. All reentrant functions are thread-safe, but not vice versa.
Q: Why is strtok() not thread-safe?
A: strtok() uses a static internal pointer to track the current position in the string. If two threads call it concurrently, they’ll corrupt this pointer. The reentrant version strtok_r() takes the state as a parameter — fully thread-safe.
Q: How does Thread-Local Storage (TLS) help?
A: TLS gives each thread its own copy of a variable. No shared state, no synchronization needed. The OS or compiler maps the TLS variable to different memory addresses for different threads.
Q: What happens when a non-reentrant function is called from a signal handler?
A: If the signal handler interrupts the program while it was executing the same function (e.g., malloc), you get undefined behavior — corruption, crash, or deadlock. This is why signal handlers should only call async-signal-safe functions.
Premium Content
Unlock Thread Safety & Reentrancy and all premium lessons with a subscription.
From ₹199.99/year — See plans