Β§27.3–27.6Locks … Summary

Part II OSTEP pp. 307–312 Β· ~5 min read

  • mutex
  • condition variable
  • spurious wakeup

27.3 Locks

For mutual exclusion over a critical section, the mutex 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 :

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 , 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:

The tempting shortcut vs. the right tool
CPU while waitingtrack recordverdict
ad-hoc spin flagburned β€” spins the whole wait~50% of uses buggy [X+10]don't EVER do this
condition variablezero β€” the waiter sleepscorrect, when used with the idiomsuse it, even when a flag seems fine
Dotted-underlined cells have explanations β€” click one.

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.

Thread API guidelines β€” the aside, tabulated (tape it above your desk)
because…
Keep it simpletricky thread interactions lead to bugs
Minimize thread interactionseach interaction is a place to be wrong
Initialize locks and CVselse: code that sometimes works, sometimes fails very strangely
Check your return codessilent failure β†’ bizarre behavior β†’ screaming, hair loss, or both
Mind argument passing & returnsa reference to stack-allocated data is probably a bug
Each thread has its own stacklocals are effectively private β€” share via the heap
Always use CVs to signalflags are tempting and wrong half the time
Use the manual pagesthe pthread man pages are highly informative
Dotted-underlined cells have explanations β€” click one.

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?

5 questions