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:
We define a
Container
struct that holds an array of counters and a mutex.The
container_init
function initializes the mutex and counters.The
inc
function increments a counter, using the mutex to ensure thread-safe access.The
doIncrement
function runs in a separate thread, repeatedly callinginc
.In
main
, we create three threads that concurrently increment the counters.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.