The race you cannot see in C
The same increment with and without a lock, run enough times to lose counts.
#include <pthread.h>
#include <stdio.h>
#define ROUNDS 200000
static long unsafe_total = 0;
static long safe_total = 0;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static void *bump(void *arg) {
(void) arg;
for (int i = 0; i < ROUNDS; i++) {
unsafe_total++;
pthread_mutex_lock(&lock);
safe_total++;
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main(void) {
pthread_t a, b;
pthread_create(&a, NULL, bump, NULL);
pthread_create(&b, NULL, bump, NULL);
pthread_join(a, NULL);
pthread_join(b, NULL);
printf("expected %d\n", 2 * ROUNDS);
printf("unsafe %ld\n", unsafe_total);
printf("safe %ld\n", safe_total);
return 0;
}
How it works
total++is three operations, not one.- Two threads interleaving them lose updates.
- The unlocked total comes out lower, and differently each run.
Keywords and builtins used here
NULLbumpforintlongmainreturnstaticvoid
The run, in numbers
- Lines
- 32
- Characters to type
- 644
- Tokens
- 167
- Three-star pace
- 105 tpm
At the three-star pace of 105 tokens a minute, this run takes about 95 seconds.
Step 2 of 4 in Locking, step 5 of 10 in Threads & concurrency.