Created
October 13, 2025 09:45
-
-
Save 7etsuo/8fa0b1042a697c802c0e3e77027de6c5 to your computer and use it in GitHub Desktop.
90% of the time, I see people using volatile when they actually need a mutex.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define _GNU_SOURCE | |
| #include <errno.h> | |
| #include <inttypes.h> | |
| #include <locale.h> | |
| #include <pthread.h> | |
| #include <sched.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <time.h> | |
| #include <unistd.h> | |
| #ifndef THREADS | |
| # define THREADS 4 | |
| #endif | |
| #ifndef ITER | |
| # define ITER 10000000L | |
| #endif | |
| #ifndef YIELD_MASK | |
| # define YIELD_MASK 0x3fff | |
| #endif | |
| _Static_assert (THREADS > 0 && THREADS <= 1024, "THREADS out of range"); | |
| _Static_assert (ITER > 0, "ITER must be positive"); | |
| #define CHECK(call) \ | |
| do \ | |
| { \ | |
| int _e = (call); \ | |
| if (_e) \ | |
| { \ | |
| fprintf (stderr, "%s failed: %s\n", \ | |
| #call, strerror (_e)); \ | |
| _Exit (1); \ | |
| } \ | |
| } \ | |
| while (0) | |
| static inline void | |
| cpu_relax (void) | |
| { | |
| #if defined(__x86_64__) || defined(__i386__) | |
| __asm__ __volatile__ ("pause"); | |
| #elif defined(__aarch64__) | |
| __asm__ __volatile__ ("yield"); | |
| #else | |
| sched_yield (); | |
| #endif | |
| } | |
| struct timing { struct timespec t0, t1; }; | |
| static inline void | |
| tstart (struct timing *t) { clock_gettime (CLOCK_MONOTONIC, &t->t0); } | |
| static inline double | |
| tstop_ms (struct timing *t) | |
| { | |
| clock_gettime (CLOCK_MONOTONIC, &t->t1); | |
| int64_t ns = (t->t1.tv_sec - t->t0.tv_sec) * 1000000000LL | |
| + (t->t1.tv_nsec - t->t0.tv_nsec); | |
| return (double) ns / 1e6; | |
| } | |
| /* WRONG: Using volatile as if it synchronized threads. */ | |
| static volatile int v_start = 0; | |
| static volatile int64_t v_counter = 0; | |
| static void * | |
| v_worker (void *arg) | |
| { | |
| (void) arg; | |
| while (!v_start) | |
| cpu_relax (); | |
| for (int64_t i = 0; i < ITER; i++) | |
| { | |
| int64_t t = v_counter; | |
| t++; | |
| v_counter = t; | |
| if ((i & YIELD_MASK) == 0) | |
| cpu_relax (); | |
| } | |
| return NULL; | |
| } | |
| static void | |
| wrong_with_volatile_demo (void) | |
| { | |
| pthread_t th[THREADS]; | |
| struct timing tm; | |
| v_counter = 0; | |
| v_start = 0; | |
| for (int i = 0; i < THREADS; i++) | |
| CHECK (pthread_create (&th[i], NULL, v_worker, NULL)); | |
| tstart (&tm); | |
| v_start = 1; | |
| for (int i = 0; i < THREADS; i++) | |
| CHECK (pthread_join (th[i], NULL)); | |
| double ms = tstop_ms (&tm); | |
| int64_t expected = (int64_t) THREADS * ITER; | |
| int64_t lost = expected - v_counter; | |
| printf ("[WRONG volatile] counter=%'" PRId64 " expected=%'" PRId64 | |
| " lost=%'" PRId64 " time=%.2f ms thr=%.2f Mops/s\n", | |
| v_counter, expected, lost, ms, (expected / ms) / 1000.0); | |
| } | |
| /* RIGHT: Mutex protects counter; condvar provides a start barrier. */ | |
| struct m_state | |
| { | |
| int64_t counter; | |
| int ready; | |
| bool start; | |
| pthread_mutex_t lock; | |
| pthread_cond_t cond; | |
| }; | |
| static struct m_state ms = | |
| { | |
| .counter = 0, | |
| .ready = 0, | |
| .start = false, | |
| .lock = PTHREAD_MUTEX_INITIALIZER, | |
| .cond = PTHREAD_COND_INITIALIZER | |
| }; | |
| static void * | |
| m_worker (void *arg) | |
| { | |
| (void) arg; | |
| CHECK (pthread_mutex_lock (&ms.lock)); | |
| ms.ready++; | |
| while (!ms.start) | |
| CHECK (pthread_cond_wait (&ms.cond, &ms.lock)); | |
| CHECK (pthread_mutex_unlock (&ms.lock)); | |
| for (int64_t i = 0; i < ITER; i++) | |
| { | |
| CHECK (pthread_mutex_lock (&ms.lock)); | |
| ms.counter++; | |
| CHECK (pthread_mutex_unlock (&ms.lock)); | |
| if ((i & YIELD_MASK) == 0) | |
| cpu_relax (); | |
| } | |
| return NULL; | |
| } | |
| static void | |
| right_with_mutex_demo (void) | |
| { | |
| pthread_t th[THREADS]; | |
| struct timing tm; | |
| ms.counter = 0; | |
| ms.ready = 0; | |
| ms.start = false; | |
| for (int i = 0; i < THREADS; i++) | |
| CHECK (pthread_create (&th[i], NULL, m_worker, NULL)); | |
| CHECK (pthread_mutex_lock (&ms.lock)); | |
| while (ms.ready < THREADS) | |
| { | |
| CHECK (pthread_mutex_unlock (&ms.lock)); | |
| struct timespec ts = { 0, 1000000 }; | |
| nanosleep (&ts, NULL); | |
| CHECK (pthread_mutex_lock (&ms.lock)); | |
| } | |
| tstart (&tm); | |
| ms.start = true; | |
| CHECK (pthread_cond_broadcast (&ms.cond)); | |
| CHECK (pthread_mutex_unlock (&ms.lock)); | |
| for (int i = 0; i < THREADS; i++) | |
| CHECK (pthread_join (th[i], NULL)); | |
| double ms_elapsed = tstop_ms (&tm); | |
| int64_t expected = (int64_t) THREADS * ITER; | |
| int64_t lost = expected - ms.counter; | |
| printf ("[RIGHT mutex] counter=%'" PRId64 " expected=%'" PRId64 | |
| " lost=%'" PRId64 " time=%.2f ms thr=%.2f Mops/s\n", | |
| ms.counter, expected, lost, ms_elapsed, | |
| (expected / ms_elapsed) / 1000.0); | |
| } | |
| int | |
| main (void) | |
| { | |
| setlocale (LC_NUMERIC, ""); | |
| printf ("THREADS=%d ITER=%'" PRId64 " YIELD_MASK=0x%X\n", | |
| THREADS, (int64_t) ITER, (unsigned) YIELD_MASK); | |
| wrong_with_volatile_demo (); | |
| right_with_mutex_demo (); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment