Dynamic Mutex, Attributes & Types

 

← Chapter 30 Index

30.1.5 / 30.1.6 / 30.1.7 — Dynamic Mutex, Attributes & Types

pthread_mutex_init, destroy, and choosing the right mutex type

Key Terms in This File

pthread_mutex_init pthread_mutex_destroy pthread_mutexattr_t PTHREAD_MUTEX_NORMAL PTHREAD_MUTEX_ERRORCHECK PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_DEFAULT lock count heap-allocated mutex

30.1.5 — Dynamically Initializing a Mutex

The static initializer PTHREAD_MUTEX_INITIALIZER only works for globally or statically declared mutexes with default attributes. In many real programs, you need to create mutexes at runtime. For this, you use pthread_mutex_init().

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

/* Both return 0 on success, or a positive error number on error */

You must use pthread_mutex_init() (instead of the static initializer) in these situations:

  • The mutex is allocated on the heap (via malloc()) — e.g., each node in a linked list has its own mutex.
  • The mutex is a local variable on the stack (automatic storage).
  • You want to set non-default attributes — e.g., a different mutex type (recursive, errorcheck).

Rules for pthread_mutex_destroy()

When a dynamically or automatically allocated mutex is no longer needed, you must destroy it to free its resources:

  • Only destroy an unlocked mutex.
  • After destroying, no thread should try to lock it again.
  • If the mutex is in a dynamically allocated memory block, destroy it before freeing that memory.
  • A stack-allocated mutex must be destroyed before the function returns.
  • You do NOT need to call pthread_mutex_destroy() on a mutex initialized with PTHREAD_MUTEX_INITIALIZER.
  • After destroying, a mutex can be reinitialized with pthread_mutex_init().

Code Example 1: Heap-Allocated Mutex (Linked List Nodes)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

/* Each node in the linked list has its own mutex for fine-grained locking */
typedef struct Node {
    int value;
    pthread_mutex_t lock;  /* Protects this node's data */
    struct Node *next;
} Node;

/* Create a new node with its own initialized mutex */
Node *create_node(int value)
{
    Node *node = malloc(sizeof(Node));
    if (node == NULL) return NULL;

    node->value = value;
    node->next  = NULL;

    /* MUST use pthread_mutex_init() for heap-allocated mutex */
    int s = pthread_mutex_init(&node->lock, NULL);  /* NULL = default attrs */
    if (s != 0) {
        free(node);
        return NULL;
    }
    return node;
}

/* Update a node's value safely */
void update_node(Node *node, int new_value)
{
    pthread_mutex_lock(&node->lock);
    node->value = new_value;
    pthread_mutex_unlock(&node->lock);
}

/* Read a node's value safely */
int read_node(Node *node)
{
    pthread_mutex_lock(&node->lock);
    int v = node->value;
    pthread_mutex_unlock(&node->lock);
    return v;
}

/* Free a node — destroy mutex BEFORE freeing memory */
void free_node(Node *node)
{
    if (node == NULL) return;

    /* Step 1: destroy the mutex */
    pthread_mutex_destroy(&node->lock);

    /* Step 2: free the memory */
    free(node);
}

int main(void)
{
    Node *n1 = create_node(10);
    Node *n2 = create_node(20);
    n1->next = n2;

    update_node(n1, 42);
    printf("n1 value = %d\n", read_node(n1));  /* 42 */
    printf("n2 value = %d\n", read_node(n2));  /* 20 */

    /* Clean up: destroy and free in order */
    free_node(n2);
    free_node(n1);
    return 0;
}

/*
 * Compile: gcc -o heap_mutex heap_mutex.c -lpthread
 */

30.1.6 / 30.1.7 — Mutex Attributes and Types

The attr argument of pthread_mutex_init() allows you to configure the mutex’s behavior. The most important attribute is the mutex type.

POSIX defines four mutex types. The type determines what happens in edge cases like re-locking or unlocking by a non-owner:

Type Re-lock by same thread Unlock by non-owner Unlock unlocked mutex Use case
PTHREAD_MUTEX_NORMAL Deadlock (hangs) Undefined (may succeed on Linux) Undefined Default — maximum performance
PTHREAD_MUTEX_ERRORCHECK Returns EDEADLK Returns error Returns error Debugging — catches misuse
PTHREAD_MUTEX_RECURSIVE Increments lock count (allowed) Returns error Returns error Re-entrant code — same thread may lock multiple times
PTHREAD_MUTEX_DEFAULT Undefined Undefined Undefined Default if NULL attrs — same as NORMAL on Linux

PTHREAD_MUTEX_RECURSIVE — Lock Count

A recursive mutex tracks how many times the same thread has locked it. Each additional lock by the same thread increments a counter. Each unlock decrements it. The mutex is only truly released when the count reaches zero.

This is useful when a function that holds a mutex calls another function that also tries to acquire the same mutex. With a normal mutex this would deadlock; with a recursive mutex it works.

Code Example 2: Setting Mutex Type (Errorcheck)

#include <stdio.h>
#include <pthread.h>

int main(void)
{
    pthread_mutex_t mtx;
    pthread_mutexattr_t mtxAttr;
    int s;

    /* Step 1: Initialize the attribute object */
    s = pthread_mutexattr_init(&mtxAttr);
    if (s != 0) { perror("mutexattr_init"); return 1; }

    /* Step 2: Set the type to ERRORCHECK */
    s = pthread_mutexattr_settype(&mtxAttr, PTHREAD_MUTEX_ERRORCHECK);
    if (s != 0) { perror("mutexattr_settype"); return 1; }

    /* Step 3: Initialize mutex with these attributes */
    s = pthread_mutex_init(&mtx, &mtxAttr);
    if (s != 0) { perror("mutex_init"); return 1; }

    /* Step 4: Destroy the attribute object (no longer needed) */
    s = pthread_mutexattr_destroy(&mtxAttr);
    if (s != 0) { perror("mutexattr_destroy"); return 1; }

    /* === Use the mutex === */
    pthread_mutex_lock(&mtx);
    printf("Locked successfully\n");

    /* Try to re-lock — with NORMAL this would deadlock.
     * With ERRORCHECK, returns EDEADLK */
    int r = pthread_mutex_lock(&mtx);
    if (r != 0)
        printf("Re-lock returned error %d (EDEADLK=%d) — good!\n",
               r, EDEADLK);

    pthread_mutex_unlock(&mtx);
    printf("Unlocked\n");

    /* === Clean up === */
    pthread_mutex_destroy(&mtx);
    return 0;
}

/*
 * Compile: gcc -o errorcheck errorcheck.c -lpthread
 * Output:
 *   Locked successfully
 *   Re-lock returned error 35 (EDEADLK=35) — good!
 *   Unlocked
 */

Code Example 3: Recursive Mutex for Re-Entrant Code

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t rec_mtx;

/* This function locks the mutex itself */
void inner_function(void)
{
    printf("inner_function: locking mutex\n");
    pthread_mutex_lock(&rec_mtx);  /* Re-lock by same thread */
    printf("inner_function: locked (lock count now 2)\n");
    /* Do work */
    pthread_mutex_unlock(&rec_mtx);  /* Decrement to 1 */
    printf("inner_function: unlocked (lock count now 1)\n");
}

/* This function also locks the mutex, then calls inner_function */
void outer_function(void)
{
    printf("outer_function: locking mutex\n");
    pthread_mutex_lock(&rec_mtx);  /* First lock (count = 1) */
    printf("outer_function: locked\n");

    inner_function();  /* Re-enters the same mutex (count = 2) */
    /* After inner returns, count is back to 1 */

    pthread_mutex_unlock(&rec_mtx);  /* Count = 0, mutex released */
    printf("outer_function: unlocked, mutex fully released\n");
}

int main(void)
{
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&rec_mtx, &attr);
    pthread_mutexattr_destroy(&attr);

    outer_function();

    pthread_mutex_destroy(&rec_mtx);
    return 0;
}

/*
 * Output:
 *   outer_function: locking mutex
 *   outer_function: locked
 *   inner_function: locking mutex
 *   inner_function: locked (lock count now 2)
 *   inner_function: unlocked (lock count now 1)
 *   outer_function: unlocked, mutex fully released
 *
 * With PTHREAD_MUTEX_NORMAL, inner_function() would deadlock.
 */

Interview Questions — Dynamic Mutex, Attributes & Types

Q1. When must you use pthread_mutex_init() instead of PTHREAD_MUTEX_INITIALIZER? (1) When the mutex is heap-allocated (e.g., inside a struct created with malloc). (2) When the mutex is a local/automatic variable on the stack. (3) When you need non-default attributes (e.g., a recursive or error-checking mutex type).
Q2. What happens if you forget to call pthread_mutex_destroy() on a dynamically allocated mutex? The internal resources used by the mutex (kernel objects, memory) are not freed. This can lead to resource leaks. Additionally, if the mutex is inside malloc’d memory and you free the memory without calling destroy, the mutex state is simply lost without proper cleanup.
Q3. What is the difference between PTHREAD_MUTEX_NORMAL and PTHREAD_MUTEX_ERRORCHECK? NORMAL gives maximum performance but no error detection — if a thread tries to re-lock a mutex it owns, it deadlocks silently; errors like unlocking a mutex you don’t own produce undefined behavior. ERRORCHECK detects all such violations and returns appropriate error codes (e.g., EDEADLK), making it useful during development and debugging.
Q4. What is a recursive mutex and when would you use it? A recursive mutex maintains a lock count. The same thread can lock it multiple times — each lock increments the count, each unlock decrements it. The mutex is only fully released when the count reaches zero. Use it when a function that holds a mutex calls another function (or itself recursively) that also tries to acquire the same mutex.
Q5. What is PTHREAD_MUTEX_DEFAULT and how does it behave on Linux? PTHREAD_MUTEX_DEFAULT is the type you get when using PTHREAD_MUTEX_INITIALIZER or passing NULL as the attr to pthread_mutex_init(). Its behavior in error cases is deliberately left undefined by POSIX (for maximum implementation flexibility). On Linux, it behaves identically to PTHREAD_MUTEX_NORMAL.
Q6. What is the correct order of operations to use a mutex attribute? (1) pthread_mutexattr_init() — initialize the attribute object. (2) pthread_mutexattr_settype() (or other setXxx calls) — set desired attributes. (3) pthread_mutex_init(&mtx, &attr) — create the mutex with those attributes. (4) pthread_mutexattr_destroy() — destroy the attribute object (it is no longer needed after mutex creation).
Q7. Can you reinitialize a mutex after destroying it? Yes. A mutex destroyed with pthread_mutex_destroy() can subsequently be reinitialized with pthread_mutex_init(). This is useful for mutex pooling or reuse.

Leave a Reply

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