30.1.1 — Statically Allocated Mutexes

 

← Chapter 30 Index

30.1.1 — Statically Allocated Mutexes

The simplest way to create a mutex — just declare and initialize at compile time

Key Terms in This File

pthread_mutex_t PTHREAD_MUTEX_INITIALIZER static allocation mutex states locked / unlocked mutex owner mutual exclusion

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
UNLOCKED → pthread_mutex_lock() → LOCKED → pthread_mutex_unlock() → UNLOCKED

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.

What is “opaque type”?
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.

Important Rule: Always use the original mutex variable, never a copy of it. According to POSIX (SUSv3), applying mutex operations to a copy of a mutex produces undefined behavior. The macro initializes specific memory in a specific way — copying it bit-by-bit to another variable does not work correctly.

/* 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(&copy);          /* 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:

1
Lock the mutex — pthread_mutex_lock(&mtx)
2
Access the shared resource (critical section)
3
Unlock the mutex — pthread_mutex_unlock(&mtx)

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)
 */
✓ With a mutex, glob is always 2,000,000. The mutex guarantees that the three-step read-increment-write sequence completes atomically for each iteration. No thread can interrupt another thread while it holds the mutex.

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

Q1. What is the data type used for a mutex in pthreads? 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.
Q2. How do you statically initialize a mutex? What does “static” mean here? Using the macro: 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.
Q3. What is the initial state of a mutex after initialization? Unlocked. After initialization (whether static or dynamic), the mutex is in the unlocked state. Any thread can immediately lock it.
Q4. Can you copy a mutex variable and use the copy? Why or why not? No. Copying a mutex and using the copy produces undefined behavior according to POSIX. Mutex operations must always be performed on the original mutex that was initialized (either via PTHREAD_MUTEX_INITIALIZER or pthread_mutex_init()).
Q5. What is mutex “ownership”? When a thread successfully locks a mutex, it becomes the owner of that mutex. Only the owner can unlock the mutex. This property prevents one thread from accidentally unlocking a mutex held by another thread.
Q6. Is mutex locking mandatory in Linux pthreads? No. Mutex locking is advisory, not mandatory. A thread can choose to ignore the mutex and directly access the shared variable. The system won’t stop it. All threads must cooperate by consistently using the same mutex to protect a shared resource. If any thread bypasses the mutex, the protection is lost.
Q7. Should you use one mutex for all shared resources or one per resource? Why? One mutex per shared resource (or per logically related group) is better. Using a single global mutex for all resources forces threads to serialize even when they could work in parallel. For example, if counter and log are independent, protecting them with separate mutexes allows Thread A to update the counter while Thread B writes the log simultaneously.

Leave a Reply

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