linux-sysprog · intermediate · ~10 min
Wrap the read-modify-write of a shared variable in `pthread_mutex_lock` / `pthread_mutex_unlock`.
You just watched two threads lose updates to a shared counter (in demonstrate-race). The cure is one of the oldest tools in concurrent programming: a mutex. Acquire it before touching the shared variable, release it after.
Every C server with shared state — a request counter, a stats table, an in-memory cache — has this exact pattern at the bottom of its critical sections. Get it right once and you never lose an update again.
Implement:
long safe_counter(int nthreads, int per_thread);
Same shape as unsafe_counter, but each counter++ happens inside pthread_mutex_lock(&m); ...; pthread_mutex_unlock(&m);. The result must be exactly nthreads * per_thread every single time.
long safe_counter(int nthreads, int per_thread);
pthread_mutex_t, not _Atomic (we want to practice the explicit lock).| nthreads | per_thread | returns |
|---|---|---|
| 1 | 1000 | 1000 |
| 4 | 10000 | 40000 (exact, every time) |
| 8 | 5000 | 40000 (exact) |
nthreads <= 0 or > 64 → return -1.counter++) into a critical section. Inside, exactly one thread executes at a time.static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;. In the worker loop: pthread_mutex_lock(&m); g_counter++; pthread_mutex_unlock(&m);.goto cleanup; to make the unlock impossible to miss.printf or sleep — fine here (tiny critical section), but in real code it stalls every other thread._Atomic long for lock-free atomic add.This is the cure for the bug you saw in demonstrate-race. With the lock in place, the result is deterministic and the program is correct.
Once you've felt the bug, the cure is satisfying. Lock-modify-unlock makes the program correct without any rewriting of the logic.
Two integers: nthreads, per_thread.
One long — exactly nthreads * per_thread.
Use pthread_mutex_t. The lock must wrap the increment, not just the read or the write.
#include <pthread.h>
long safe_counter(int nthreads, int per_thread) {
/* TODO: same as unsafe_counter, but protect g_counter with a mutex. */
return -1;
}
Locking around the read but not the write. Forgetting pthread_mutex_unlock on an early return. Holding the mutex across I/O.
Single thread: counter equals per_thread (no race possible). Many threads: counter equals nthreads * per_thread every time.
Solve this exercise in the browser editor — compile and run against the test harness, no setup required.