Skip to content

Instantly share code, notes, and snippets.

@7etsuo
Created October 13, 2025 09:45
Show Gist options
  • Select an option

  • Save 7etsuo/8fa0b1042a697c802c0e3e77027de6c5 to your computer and use it in GitHub Desktop.

Select an option

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.
#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