30.1.1 — Statically Allocated Mutexes
The simplest way to create a mutex — just declare and initialize at compile time
Quick Recap: What Is a Mutex?
A mutex (short for mutual exclusion lock) is like a key to a room. Only one thread can hold the key at a time. While one thread holds the key (has the mutex locked), all other threads that want the key must wait outside (block).
Once the thread with the key leaves the room and puts the key back (unlocks the mutex), one of the waiting threads picks it up and enters.
This ensures that only one thread at a time can access the protected shared resource — eliminating race conditions.
The Two States of a Mutex
Mutex State Machine
🔓 UNLOCKED State
- Initial state after initialization
- Any thread can acquire it
- First thread to call
lock()wins it - No owner
🔒 LOCKED State
- Exactly one thread owns it
- Only the owner can unlock it
- Other threads trying to lock → they block (wait)
- Has an owner
The Mutex Data Type: pthread_mutex_t
In the pthreads library, a mutex is represented by a variable of type pthread_mutex_t. This is an opaque type — you don’t need to know its internal structure. You just declare a variable of this type and then call pthreads functions to operate on it.
Opaque means the internal implementation details are hidden. You interact with it only through the API functions. Think of it like a TV remote — you press buttons, you don’t need to know the electronics inside.
Static Initialization: PTHREAD_MUTEX_INITIALIZER
The simplest way to initialize a mutex is using the macro PTHREAD_MUTEX_INITIALIZER at the point of declaration. This is called static initialization because the mutex is initialized at compile time.
/* Declare and initialize a mutex statically */
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
This is equivalent to calling pthread_mutex_init() with default attributes (which we cover in section 30.1.5). For most programs, the static initializer is all you need.
/* CORRECT */
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mtx); /* Always use the original */
/* WRONG — don't do this */
pthread_mutex_t copy = mtx; /* Copying is undefined */
pthread_mutex_lock(©); /* Behavior undefined */
The Protocol for Using a Mutex
All threads must follow this three-step protocol when accessing a shared resource protected by a mutex:
Code Example 1: Declaring Multiple Mutexes for Multiple Resources
#include <pthread.h>
#include <stdio.h>
/* One mutex per shared resource is the standard pattern */
static pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
static int counter = 0;
static int log_count = 0;
void increment_counter(void)
{
/* Lock only the counter mutex — log can be accessed by other threads */
pthread_mutex_lock(&counter_mutex);
counter++;
pthread_mutex_unlock(&counter_mutex);
}
void write_log(const char *msg)
{
/* Lock only the log mutex */
pthread_mutex_lock(&log_mutex);
printf("[LOG %d] %s\n", ++log_count, msg);
pthread_mutex_unlock(&log_mutex);
}
/* Using separate mutexes for separate resources allows more parallelism.
* Thread A can increment counter while Thread B writes to log. */
int main(void)
{
increment_counter();
write_log("Counter incremented");
printf("counter = %d\n", counter);
return 0;
}
/*
* Compile: gcc -o mutex_static mutex_static.c -lpthread
*/
Code Example 2: The Correct Increment Program (Static Mutex)
This is the fixed version of the broken program from section 30.1. We add a static mutex and wrap the critical section with lock/unlock.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
static int glob = 0;
/* Static mutex initialization — the simplest approach */
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static void *threadFunc(void *arg)
{
int loops = *((int *) arg);
int loc, j;
for (j = 0; j < loops; j++) {
/* LOCK: only one thread can be here at a time */
pthread_mutex_lock(&mtx);
loc = glob; /* Critical section start */
loc++;
glob = loc; /* Critical section end */
/* UNLOCK: let other threads in */
pthread_mutex_unlock(&mtx);
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t t1, t2;
int loops = (argc > 1) ? atoi(argv[1]) : 1000000;
pthread_create(&t1, NULL, threadFunc, &loops);
pthread_create(&t2, NULL, threadFunc, &loops);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
/* Now always correct! */
printf("glob = %d (expected = %d)\n", glob, 2 * loops);
return 0;
}
/*
* Compile: gcc -o thread_correct thread_correct.c -lpthread
*
* Output (always correct now):
* glob = 2000000 (expected = 2000000)
*/
How Does Figure 30-2 (Thread Blocking) Work?
Here is what happens when two threads try to enter the critical section simultaneously:
| Thread A | Thread B |
|---|---|
| pthread_mutex_lock(mtx) → succeeds, gets lock | |
| access shared resource… | pthread_mutex_lock(mtx) → BLOCKS (waits) |
| pthread_mutex_unlock(mtx) → releases lock | |
| unblocks, gets lock → access shared resource |
Interview Questions — Statically Allocated Mutexes
pthread_mutex_t. It is an opaque type defined in <pthread.h>. You never access its internal fields directly — you only use pthreads API functions to operate on it.pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;. “Static” here means the mutex is initialized at the time of variable declaration (compile-time), not at runtime. It is typically used for global or file-scope variables.