Mutexes in C

In this example, we’ll explore how to use mutexes to safely access data across multiple threads. This is useful for managing more complex state than what can be handled with simple atomic operations.

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

// Container holds a map of counters; since we want to
// update it concurrently from multiple threads, we
// add a mutex to synchronize access.
typedef struct {
    pthread_mutex_t mu;
    int counters[2];
} Container;

// Initialize the mutex before using it
void container_init(Container *c) {
    pthread_mutex_init(&c->mu, NULL);
    c->counters[0] = 0;  // "a"
    c->counters[1] = 0;  // "b"
}

// Lock the mutex before accessing counters; unlock
// it at the end of the function.
void inc(Container *c, int index) {
    pthread_mutex_lock(&c->mu);
    c->counters[index]++;
    pthread_mutex_unlock(&c->mu);
}

// This function increments a named counter
// in a loop.
void* doIncrement(void* arg) {
    Container *c = ((void**)arg)[0];
    int index = *(int*)((void**)arg)[1];
    int n = *(int*)((void**)arg)[2];

    for (int i = 0; i < n; i++) {
        inc(c, index);
    }

    return NULL;
}

int main() {
    Container c;
    container_init(&c);

    pthread_t threads[3];
    int index_a = 0, index_b = 1, count = 10000;

    // Run several threads concurrently; note
    // that they all access the same Container,
    // and two of them access the same counter.
    void* args1[] = {&c, &index_a, &count};
    void* args2[] = {&c, &index_a, &count};
    void* args3[] = {&c, &index_b, &count};

    pthread_create(&threads[0], NULL, doIncrement, args1);
    pthread_create(&threads[1], NULL, doIncrement, args2);
    pthread_create(&threads[2], NULL, doIncrement, args3);

    // Wait for the threads to finish
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }

    printf("counters: a=%d, b=%d\n", c.counters[0], c.counters[1]);

    pthread_mutex_destroy(&c.mu);
    return 0;
}

This C program demonstrates the use of mutexes to safely manage concurrent access to shared data. Here’s a breakdown of the key components:

  1. We define a Container struct that holds an array of counters and a mutex.

  2. The container_init function initializes the mutex and counters.

  3. The inc function increments a counter, using the mutex to ensure thread-safe access.

  4. The doIncrement function runs in a separate thread, repeatedly calling inc.

  5. In main, we create three threads that concurrently increment the counters.

  6. After all threads complete, we print the final counter values.

To compile and run the program:

$ gcc -o mutexes mutexes.c -lpthread
$ ./mutexes
counters: a=20000, b=10000

This output shows that the counters were updated as expected, with proper synchronization preventing race conditions.

Note that C doesn’t have built-in support for maps like Go does, so we’ve simplified the example to use an array instead. Also, C requires manual memory management and explicit thread creation/joining, which adds some complexity compared to the Go version.