27.3 Locks
For mutual exclusion over a critical section, the mutex mutex POSIX's lock object (pthread_mutex_t): lock() blocks until the caller holds it, unlock() releases. Must be INITIALIZED (static initializer or pthread_mutex_init) and its return codes CHECKED β the two classic omissions. trylock/timedlock variants exist; mostly avoid them. defined in ch. 27 β open in glossary pair:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
The intended usage is exactly what youβd imagine:
pthread_mutex_t lock;
pthread_mutex_lock(&lock);
x = x + 1; // or whatever your critical section is
pthread_mutex_unlock(&lock);
Semantics: if no thread holds the lock, the caller acquires it and enters; if another thread holds it, the call does not return until the caller has the lock (many threads may be stuck waiting; only the holder should unlock). And yet β that snippet is broken, in two important ways:
Flaw 1 β no initialization. Locks must start with correct values. Either the static way:
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
or the dynamic way (the authorsβ usual choice β with a matching
pthread_mutex_destroy() when done):
int rc = pthread_mutex_init(&lock, NULL);
assert(rc == 0); // always check success!
Flaw 2 β unchecked error codes. These routines can fail β and an unchecked failure fails silently, potentially admitting multiple threads into your critical section. Minimally, wrap:
// Keeps code clean; only use if exit() OK upon failure
void Pthread_mutex_lock(pthread_mutex_t *mutex) {
int rc = pthread_mutex_lock(mutex);
assert(rc == 0);
}
Two more acquisition variants exist β trylock (returns failure if
the lock is held) and timedlock (gives up after a timeout). Both
should generally be avoided; a few situations β notably dodging
deadlock, a coming chapter β make them useful.
27.4 Condition Variables
When one thread must wait for another, the condition variable condition variable The signaling primitive (pthread_cond_t): wait(cond, mutex) sleeps the caller β releasing the mutex while asleep, reacquiring before return β until another thread signal()s. Always called with the lock held; always re-checked in a while loop. The deep treatment arrives with ch30. defined in ch. 27 β open in glossary :
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);
A CV always travels with an associated lock, held when calling either routine. The two sides of the conversation:
The waiter
Pthread_mutex_lock(&lock);
while (ready == 0)
Pthread_cond_wait(&cond, &lock);
Pthread_mutex_unlock(&lock);The signaler
Pthread_mutex_lock(&lock);
ready = 1;
Pthread_cond_signal(&cond);
Pthread_mutex_unlock(&lock);Three details carry all the weight. The lock is held around both state change and signal β thatβs what keeps races out of your signaling. Wait takes the mutex because it releases it: the caller goes to sleep and lets go of the lock (imagine it didnβt β how could the signaler ever get in?), then re-acquires it before returning. And the condition is re-checked in a while loop, not an if: some implementations can wake a waiter spuriously spurious wakeup Some pthread implementations can wake a waiting thread when nothing actually changed β which is why the condition is re-checked in a WHILE loop, never an if: treat waking as a hint, not a fact. defined in ch. 27 β open in glossary , so treat waking as a hint that something might have changed β never as a fact.
The tempting alternative β just spin on a flag β has a scoreboard:
| CPU while waiting | track record | verdict | |
|---|---|---|---|
| ad-hoc spin flag | burned β spins the whole wait | ~50% of uses buggy [X+10] | don't EVER do this |
| condition variable | zero β the waiter sleeps | correct, when used with the idioms | use it, even when a flag seems fine |
If CVs still feel confusing, donβt worry too much (yet) β a whole chapter is coming. Until then: they exist, and this is how and why theyβre used.
27.5 Compiling And Running
Include pthread.h; link with -pthread:
prompt> gcc -o main main.c -Wall -pthread
You have now successfully compiled a concurrent program. Whether it works is, as usual, a different matter entirely.
27.6 Summary
Creation and join; mutual exclusion via locks; signaling and waiting via condition variables. You donβt need much else to write robust, efficient multi-threaded code β except patience and a great deal of care. The hard part with threads is not the APIs; itβs the tricky logic of building concurrent programs. Read on.
| because⦠| |
|---|---|
| Keep it simple | tricky thread interactions lead to bugs |
| Minimize thread interactions | each interaction is a place to be wrong |
| Initialize locks and CVs | else: code that sometimes works, sometimes fails very strangely |
| Check your return codes | silent failure β bizarre behavior β screaming, hair loss, or both |
| Mind argument passing & returns | a reference to stack-allocated data is probably a bug |
| Each thread has its own stack | locals are effectively private β share via the heap |
| Always use CVs to signal | flags are tempting and wrong half the time |
| Use the manual pages | the pthread man pages are highly informative |
Homework (Code): helgrind
Hunt races with a real tool: run valgrind βtool=helgrind on main-race.c and see it point at the offending lines; add locks and watch the reports change; feed it a deadlock and a false-positive variant to learn a toolβs limits; and compare the flag-based main-signal.c (correct? efficient?) against main-signal-cv.c. Get it at ostep-homework.Check yourself
1.The four-line lock/x++/unlock snippet 'is broken, in two important ways.' Which two?
2.When does pthread_mutex_lock return to its caller β and what do trylock and timedlock change?
3.pthread_cond_wait takes the mutex as a second argument; pthread_cond_signal doesn't. Why does wait need it?
4.Why must the waiting thread re-check its condition in a WHILE loop rather than an if?
5.Instead of a CV, one could spin: while (ready == 0); with ready = 1 in the other thread. The book says: don't EVER do this. Both reasons?