C: protecting a shared counter with a POSIX mutex
Run two threads that increment one counter and use pthread_mutex_lock so no update is lost.
On this page
C pointers and function pointers.
- Run two threads that increment one counter and use pthread_mutex_lock so no update is lost.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
POSIX threads (pthreads) let one C program run several threads in the same address space. They share global variables, which is powerful and dangerous. counter++ is a load, an add, and a store, so two threads can interleave and lose updates. Unsynchronized access is a data race, and in C it is undefined behaviour.
A mutex gives mutual exclusion. A thread calls pthread_mutex_lock before touching the shared data and pthread_mutex_unlock after. Only one thread can hold the lock at a time. pthread_join waits for a thread to finish, which also ensures its writes are visible. Link with -pthread on Linux.
Code example
#include <pthread.h>
#include <stdio.h>
static int counter = 0;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static void *work(void *arg) {
(void)arg;
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&lock);
counter++;
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main(void) {
pthread_t a, b;
pthread_create(&a, NULL, work, NULL);
pthread_create(&b, NULL, work, NULL);
pthread_join(a, NULL);
pthread_join(b, NULL);
printf("counter=%d\n", counter);
return 0;
}counter=200000
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
#include <pthread.h>#include <stdio.h> static int counter = 0;static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; static void *work(void *arg) { (void)arg; for (int i = 0; i < 100000; i++) { pthread_mutex_lock(&lock); counter++; pthread_mutex_unlock(&lock); } return NULL;} int main(void) { pthread_t a, b; pthread_create(&a, NULL, work, NULL); pthread_create(&b, NULL, work, NULL); pthread_join(a, NULL); pthread_join(b, NULL); printf("counter=%d\n", counter); return 0;}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| A statically initialized mutex shared by both threads. | — | — | |
| Only one thread can pass this point at a time. | — | — | |
| Both threads now run work concurrently. | — | — | |
| Wait for both threads before reading counter. | — | — | |
| Each thread made 100000 protected increments. | counter = 200000 | counter=200000 |
Behind the scenes
How C processes this code
The C compiler produces a native program. Variables occupy memory, and execution enters main.
Cost and common mistakes
O(n) total work; the lock serializes the increments.
- Forgetting to unlock leaves every other thread waiting forever, which is a deadlock.
- Locking inside the innermost loop is slow. Count locally and add the total under the lock once.
- Check the return values of pthread_create and pthread_join in real programs.
Try it yourself
What would happen without the mutex?
Reveal answer and reasoning
The result would usually be below 200000 and would change from run to run, because updates are lost.