Skip to content

Instantly share code, notes, and snippets.

@grahamking
Created May 26, 2025 20:39
Show Gist options
  • Select an option

  • Save grahamking/415b487f754de048ecd34a6c841012df to your computer and use it in GitHub Desktop.

Select an option

Save grahamking/415b487f754de048ecd34a6c841012df to your computer and use it in GitHub Desktop.
use std::{
sync::atomic::{AtomicPtr, Ordering},
time::Duration,
};
const ONE_MS: Duration = Duration::from_millis(1);
const LOOPS: usize = 1000;
const ORDERING: Ordering = Ordering::Relaxed;
struct User {
name: String,
permissions: u64,
}
impl User {
fn make(i: usize) -> Box<Self> {
Box::new(Self {
name: format!("name_{i}"),
permissions: i as u64,
})
}
}
fn main() {
let on_call_ptr = AtomicPtr::new(Box::into_raw(User::make(0)));
// The scope avoids needing an `Arc`
std::thread::scope(|s| {
// Writer
s.spawn(|| writer(&on_call_ptr));
// Readers
for thread_idx in 0..3 {
let on_call_ptr = &on_call_ptr;
s.spawn(move || reader(thread_idx, on_call_ptr));
}
});
std::thread::sleep(ONE_MS * (LOOPS as u32 + 100));
}
/// Change who is on call, occasionally, from a single thread.
#[inline(never)]
fn writer(on_call_ptr: &AtomicPtr<User>) {
for user_id in 1..(LOOPS / 10) {
std::thread::sleep(ONE_MS * 10);
let user_ptr = Box::into_raw(User::make(user_id));
// Atomically set the next user as on-call
let old = on_call_ptr.swap(user_ptr, ORDERING);
println!("--- goes on call: {user_id}");
// De-allocate previous user
unsafe { drop(Box::from_raw(old)) };
}
}
/// Read who is on call, often and from many threads.
#[inline(never)]
fn reader(id: usize, on_call_ptr: &AtomicPtr<User>) {
for l in 0..LOOPS {
// Only the readers access the internals of User, hence it is safe.
let on_call = unsafe { &*on_call_ptr.load(ORDERING) };
if id == 0 && l % 10 == 0 {
println!("thread {id}: {} {}", on_call.name, on_call.permissions);
}
std::thread::sleep(ONE_MS);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment