30.1.2 — Locking and Unlocking a Mutex

 

← Chapter 30 Index

30.1.2 — Locking and Unlocking a Mutex

pthread_mutex_lock, unlock, trylock, timedlock — all four variants explained

Key Terms in This File

pthread_mutex_lock pthread_mutex_unlock pthread_mutex_trylock pthread_mutex_timedlock EBUSY ETIMEDOUT EDEADLK blocking non-blocking

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.

#include <pthread.h>

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.

#include <pthread.h>

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)
When should you use trylock? Use it sparingly. In well-designed programs, threads should hold mutexes for very short periods. Using trylock to periodically poll a mutex can cause starvation — the polling thread may keep losing the race to other threads that are properly blocking with lock(). Use trylock only when you have clear alternative work to do if the lock is unavailable, or in the deadlock-avoidance strategy (Section 30.1.4).

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 <pthread.h>
#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 */

Absolute time, not relative: The 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

Q1. What is the difference between pthread_mutex_lock() and pthread_mutex_trylock()? 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.
Q2. What error does pthread_mutex_trylock() return when the mutex is already locked? It returns EBUSY.
Q3. What happens if a thread calls pthread_mutex_lock() on a mutex it already holds? For the default mutex type on Linux (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.)
Q4. What error does pthread_mutex_timedlock() return on timeout? ETIMEDOUT.
Q5. Is the abstime argument to pthread_mutex_timedlock() a duration or an absolute time? It is an absolute time — seconds since the Unix Epoch (January 1, 1970). To specify “wait 5 seconds”, you get the current time with clock_gettime(CLOCK_REALTIME, ...) and add 5 to tv_sec.
Q6. Can a thread unlock a mutex it does not own? No. Unlocking a mutex that is not held by the calling thread, or that is not locked at all, is an error. For the default mutex type, the behavior is undefined. For PTHREAD_MUTEX_ERRORCHECK, an error is returned.
Q7. If multiple threads are waiting on the same mutex, which one gets it when unlock is called? It is indeterminate — the POSIX standard does not specify which waiting thread will be granted the lock next. The OS scheduler decides. You should not write code that depends on a particular thread being scheduled first.
Q8. Why is pthread_mutex_trylock() not recommended for general use? Using trylock to periodically poll can cause starvation — the polling thread may repeatedly miss the lock while other threads that are properly blocking with pthread_mutex_lock() get it. Also, well-designed programs keep critical sections short, so other threads rarely wait long. Only use trylock when you have a genuine reason not to block.

Leave a Reply

Your email address will not be published. Required fields are marked *