C · Course navigation

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
Before you start

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

C
#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;
}
Output
counter=200000

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1#include <pthread.h>
2#include <stdio.h>
3
4static int counter = 0;
5static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
6
7static void *work(void *arg) {
8 (void)arg;
9 for (int i = 0; i < 100000; i++) {
10 pthread_mutex_lock(&lock);
11 counter++;
12 pthread_mutex_unlock(&lock);
13 }
14 return NULL;
15}
16
17int main(void) {
18 pthread_t a, b;
19 pthread_create(&a, NULL, work, NULL);
20 pthread_create(&b, NULL, work, NULL);
21 pthread_join(a, NULL);
22 pthread_join(b, NULL);
23 printf("counter=%d\n", counter);
24 return 0;
25}

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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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.

Further reading