30.1.2 — Locking and Unlocking a Mutex
pthread_mutex_lock, unlock, trylock, timedlock — all four variants explained
The Core Functions: Lock and Unlock
After a mutex is initialized (in the unlocked state), you use two primary functions to control access to your critical section:
pthread_mutex_lock()— Lock the mutex (block if already locked by another thread)pthread_mutex_unlock()— Release the mutex so other threads can acquire it
Both functions return 0 on success, or a positive error number on failure.
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
/* Both return 0 on success, or a positive error number on error */
How pthread_mutex_lock() Works
Case 1: Mutex is currently unlocked
The calling thread immediately acquires the lock and returns. Execution continues past the lock call.
Case 2: Mutex is currently locked by another thread
The calling thread blocks (goes to sleep). It stays blocked until the owning thread calls pthread_mutex_unlock(). Then it wakes up, acquires the lock, and returns.
Case 3: Mutex is already locked by the same thread (self-deadlock)
For the default mutex type on Linux: the thread deadlocks — it blocks forever trying to lock a mutex it already owns. (Some mutex types handle this differently — see section 30.1.7.)
How pthread_mutex_unlock() Works
Unlock releases the mutex held by the calling thread. Important rules:
- Only the owner (the thread that locked it) can unlock it.
- Unlocking a mutex that is not locked → error.
- Unlocking a mutex locked by a different thread → error.
- If multiple threads are waiting, it is indeterminate which one gets the lock next — the OS scheduler decides.
Code Example 1: Basic Lock/Unlock Pattern with Error Checking
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
static int shared_count = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static void *worker(void *arg)
{
int id = *((int *) arg);
int s;
for (int i = 0; i < 500000; i++) {
/* Lock — always check return value in production code */
s = pthread_mutex_lock(&mtx);
if (s != 0) {
fprintf(stderr, "Thread %d: lock failed: %d\n", id, s);
return NULL;
}
/* --- CRITICAL SECTION --- */
shared_count++;
/* --- END CRITICAL SECTION --- */
s = pthread_mutex_unlock(&mtx);
if (s != 0) {
fprintf(stderr, "Thread %d: unlock failed: %d\n", id, s);
return NULL;
}
}
printf("Thread %d done\n", id);
return NULL;
}
int main(void)
{
pthread_t t1, t2;
int id1 = 1, id2 = 2;
pthread_create(&t1, NULL, worker, &id1);
pthread_create(&t2, NULL, worker, &id2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("shared_count = %d (expected 1000000)\n", shared_count);
return 0;
}
/*
* Compile: gcc -o lock_unlock lock_unlock.c -lpthread
* Output:
* Thread 1 done
* Thread 2 done
* shared_count = 1000000 (expected 1000000)
*/
Non-Blocking Variant: pthread_mutex_trylock()
Sometimes a thread cannot afford to block and wait. For example, it might have other useful work it can do instead of sleeping. pthread_mutex_trylock() attempts to lock the mutex but never blocks.
int pthread_mutex_trylock(pthread_mutex_t *mutex);
/* Returns 0 if lock acquired */ /* Returns EBUSY if mutex is currently locked by another thread */
| Situation | pthread_mutex_lock() | pthread_mutex_trylock() |
|---|---|---|
| Mutex is unlocked | Acquires lock, returns 0 | Acquires lock, returns 0 |
| Mutex is locked by other thread | BLOCKS (sleeps) until unlocked | Returns EBUSY immediately (no block) |
| Mutex is locked by same thread (default type) | Deadlocks | Deadlocks (same behavior) |
Code Example 2: Using trylock to Do Alternative Work
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
static pthread_mutex_t resource_mtx = PTHREAD_MUTEX_INITIALIZER;
static int shared_data = 0;
void do_other_work(int thread_id)
{
/* Simulates work that doesn't need the shared resource */
printf("Thread %d: doing other work while lock unavailable\n", thread_id);
}
static void *worker(void *arg)
{
int id = *((int *) arg);
int attempts = 0;
while (1) {
int s = pthread_mutex_trylock(&resource_mtx);
if (s == 0) {
/* Got the lock! */
shared_data++;
printf("Thread %d: acquired lock (attempt %d), data = %d\n",
id, ++attempts, shared_data);
pthread_mutex_unlock(&resource_mtx);
break; /* Done */
} else if (s == EBUSY) {
/* Lock not available — do something else */
do_other_work(id);
/* Small delay before trying again */
struct timespec ts = {0, 1000000}; /* 1ms */
nanosleep(&ts, NULL);
} else {
/* Unexpected error */
fprintf(stderr, "trylock error: %d\n", s);
break;
}
}
return NULL;
}
int main(void)
{
pthread_t t1, t2;
int id1 = 1, id2 = 2;
pthread_create(&t1, NULL, worker, &id1);
pthread_create(&t2, NULL, worker, &id2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
/*
* Compile: gcc -o trylock_demo trylock_demo.c -lpthread
*/
Timed Variant: pthread_mutex_timedlock()
This function works like pthread_mutex_lock() but with a timeout. If the mutex cannot be acquired within the specified time, it returns ETIMEDOUT.
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *mutex,
const struct timespec *abstime);
/* abstime = absolute time (since Epoch), NOT a duration */ /* Returns ETIMEDOUT if time expires before lock is acquired */
abstime argument is an absolute time (seconds since the Unix Epoch: Jan 1 1970), not a duration. To wait for “2 seconds from now”, you get the current time and add 2 seconds to it.#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <pthread.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
int try_lock_with_timeout(int wait_seconds)
{
struct timespec deadline;
/* Get current time */
clock_gettime(CLOCK_REALTIME, &deadline);
/* Add timeout to get absolute deadline */
deadline.tv_sec += wait_seconds;
int s = pthread_mutex_timedlock(&mtx, &deadline);
if (s == 0) {
printf("Lock acquired within %d seconds\n", wait_seconds);
/* ... do work ... */
pthread_mutex_unlock(&mtx);
return 1; /* success */
} else if (s == ETIMEDOUT) {
printf("Timeout! Could not acquire lock within %d seconds\n",
wait_seconds);
return 0; /* timeout */
} else {
fprintf(stderr, "timedlock error: %d\n", s);
return -1; /* error */
}
}
int main(void)
{
/* Simulate: hold the lock so timedlock will timeout */
pthread_mutex_lock(&mtx);
printf("Main: holding lock...\n");
/* This will timeout after 1 second since we hold the lock */
try_lock_with_timeout(1);
pthread_mutex_unlock(&mtx);
printf("Main: released lock\n");
/* Now this should succeed */
try_lock_with_timeout(1);
return 0;
}
/*
* Compile: gcc -o timedlock timedlock.c -lpthread
*
* Output:
* Main: holding lock...
* Timeout! Could not acquire lock within 1 seconds
* Main: released lock
* Lock acquired within 1 seconds
*/
Summary: When to Use Which Function
| Function | Blocks? | Use When |
|---|---|---|
pthread_mutex_lock() |
Yes (indefinitely) | Most common case — thread must wait until lock is available |
pthread_mutex_trylock() |
No | Thread has alternative work if lock unavailable; deadlock avoidance |
pthread_mutex_timedlock() |
Yes (until timeout) | Thread can wait, but not forever; real-time/deadline systems |
pthread_mutex_unlock() |
Never | Always after critical section; only call if you own the lock |
Interview Questions — Lock, Unlock, Trylock, Timedlock
pthread_mutex_lock() blocks the calling thread until the mutex is available. pthread_mutex_trylock() is non-blocking — if the mutex is locked, it returns immediately with EBUSY instead of sleeping.EBUSY.PTHREAD_MUTEX_NORMAL), the thread deadlocks — it blocks trying to acquire a mutex it already owns, and waits forever. No error is returned. (With PTHREAD_MUTEX_ERRORCHECK type, it returns EDEADLK instead.)ETIMEDOUT.clock_gettime(CLOCK_REALTIME, ...) and add 5 to tv_sec.PTHREAD_MUTEX_ERRORCHECK, an error is returned.