Linux System Programming · intermediate · ~10 min
Learn to recognize the threading bugs that trip up almost every C programmer when they first use pthreads.
Here are eight mistakes that show up again and again in threaded C programs. Each one has a clear cause and a clear fix.
-pthreadLeave this flag off and you get a linker error — or worse. On some platforms the code compiles, but pthread_mutex calls silently turn into no-ops, so your locking does nothing.
Always compile and link with -pthread.
&i from a loopIf you start threads inside a loop and pass the address of the loop variable i, every thread ends up reading the same memory. By the time the threads run, i already holds its post-loop value, so they all see it.
Pass the value itself, or pass a pointer to memory allocated on the heap for each thread.
counter++ looks like one step, but it is really three: read, add, write. Two threads can interleave these steps and lose updates. This is a data race.
Protect the counter with a mutex, or declare it as _Atomic long.
If a thread keeps a lock held while it waits on slow I/O, every other thread that needs that lock stalls too.
Release the lock before doing I/O.
If one path locks A then B, and another locks B then A, the two will eventually collide and wait on each other forever. This is a deadlock.
Adopt a single global rule for the order in which locks are acquired, and follow it everywhere.
signal() in a threaded programThe old signal() function has poorly defined behavior with threads.
Use sigaction() instead. Consider blocking signals on worker threads with pthread_sigmask.
if instead of while around cond_waitA condition variable can wake a thread even when nothing changed — a spurious wakeup. If you check the condition with if, the thread continues on a false assumption.
Always re-check the condition in a while loop.
The thread's stack disappears the moment the thread exits. Any pointer into it becomes invalid.
Return a heap pointer or a value, never the address of a local variable.
/* WRONG — race on shared counter */
static int counter = 0;
void *bump(void *_) { for (int i = 0; i < 1000000; i++) counter++; return NULL; }
/* RIGHT — atomic */
static _Atomic int counter = 0;
void *bump(void *_) { for (int i = 0; i < 1000000; i++) counter++; return NULL; }
-fsanitize=thread (TSan) before shipping. It catches data races that testing alone will miss.-pthread, and pass thread arguments safely (by value or by heap pointer).while (not if) around cond_wait.-fsanitize=thread) before shipping.