This section introduces the fundamental tool for thread synchronization: the Mutex (short for Mutual Exclusion).
Core Objective: Protecting the “Critical Region”
The primary goal of a mutex is to protect a critical region. A critical region is a block of code that accesses or modifies shared data (like a global counter or a linked list).
- Without a Mutex: Multiple threads might enter this region at the same time, overwriting each other’s changes (the “race condition” we discussed in Volume 1).
- With a Mutex: We ensure that only one thread executes the code within the critical region at any given time.
1. The Code Pattern
The logic is simple and must always follow this pattern:
lock_the_mutex(...);
/* CRITICAL REGION */
/* Access shared data here */
unlock_the_mutex(...);Since only one thread can hold the lock at a time, if a second thread tries to lock it while the first thread is inside, the second thread is forced to wait (block) until the first thread unlocks it.
2. The API Functions
Stevens introduces the three basic functions required to manipulate a mutex. A. Locking and Unlocking
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);pthread_mutex_lock: If the mutex is unlocked, it locks it and returns immediately. If the mutex is already locked by another thread, this function blocks (sleeps) the calling thread until the mutex becomes available.pthread_mutex_unlock: Releases the lock, allowing a waiting thread to proceed.
B. Non-Blocking Lock (trylock)
int pthread_mutex_trylock(pthread_mutex_t *mptr);- This function never puts the thread to sleep.
- If the mutex is free, it locks it.
- If the mutex is already locked, it returns an error
EBUSYimmediately.
Note
This is useful if you want your thread to go do other work instead of waiting.
3. Initialization
Before you can use a mutex, it must be initialized. The book distinguishes between static and dynamic allocation:
- Static Initialization: If your mutex is a global variable or a static variable, you can initialize it with a special constant:
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;This is the most common way for simple programs.
- Dynamic Initialization: If you allocate a mutex using
malloc(or in shared memory), you must callpthread_mutex_init(which we will cover in Section 7.7). Note: Omitting initialization is a common bug!
4. Important Concept: “Cooperative Locks”
Stevens emphasizes that mutexes are cooperative locks.
- Meaning: The mutex prevents access only if all threads agree to check the lock before touching the data.
- The Danger: If Thread A locks the mutex to update a variable, but Thread B writes to that variable without checking the lock first, the system cannot stop it. The code is “broken” because the threads are not cooperating.