### Atomic Operation Example with FUTEX_WAKE_OP Source: https://marabos.nl/atomics/os-primitives Illustrates the atomic sequence of modifying a secondary atomic variable and waking threads based on a condition. This example simulates the behavior of FUTEX_WAKE_OP, which combines an atomic fetch-and-update operation with conditional thread waking. ```Rust use std::sync::atomic::AtomicI32; use std::sync::atomic::Ordering::Relaxed; fn simulate_futex_wake_op( atomic1: &AtomicI32, atomic2: &AtomicI32, n_wake1: i32, m_wake2: i32, operation: impl Fn(i32) -> i32, condition: impl Fn(Option) -> bool, ) -> i32 { // Simulate atomic fetch_update on atomic2 let old_val2 = atomic2.fetch_update(Relaxed, Relaxed, |x| Some(operation(x))); // Simulate waking threads on atomic1 // In a real scenario, this would involve syscalls. println!("Waking {} threads on atomic1", n_wake1); // Simulate conditional waking on atomic2 if condition(old_val2) { println!("Waking {} threads on atomic2", m_wake2); return n_wake1 + m_wake2; // Total awoken threads } else { return n_wake1; } } fn main() { let atomic1 = AtomicI32::new(0); let atomic2 = AtomicI32::new(5); // Example: Add 2 to atomic2, wake 1 thread on atomic1, and if the old value was 5, wake 1 thread on atomic2. let total_awoken = simulate_futex_wake_op( &atomic1, &atomic2, 1, // n_wake1 1, // m_wake2 |x| x + 2, // some_operation: add 2 |old| old == Some(5), // some_condition: check if old value was 5 ); println!("Total threads awoken: {}", total_awoken); println!("Final value of atomic2: {}", atomic2.load(Relaxed)); } ``` -------------------------------- ### Assembly Code Example with Labels (Made-up Architecture) Source: https://marabos.nl/atomics/hardware This example shows the same assembly operations as the previous one but utilizes human-readable labels instead of direct memory addresses. Labels are placeholders that an assembler replaces with actual memory addresses during compilation, improving code readability. ```assembly ldr x, SOME_VAR li y, 0 my_loop: inc x add y, x mul x, 3 cmp y, 10 jne my_loop str SOME_VAR, x ``` -------------------------------- ### Assembly Code Example (Made-up Architecture) Source: https://marabos.nl/atomics/hardware This snippet demonstrates basic assembly instructions for a hypothetical processor architecture. It includes operations like loading from memory, initializing registers, incrementing, addition, multiplication, comparison, and conditional jumps, illustrating common low-level programming constructs. ```assembly ldr x, 1234 // load from memory address 1234 into x li y, 0 // set y to zero inc x // increment x add y, x // add x to y mul x, 3 // multiply x by 3 cmp y, 10 // compare y to 10 jne -5 // jump five instructions back if not equal str 1234, x // store x to memory address 1234 ``` -------------------------------- ### Rust: Annoying Function Example Source: https://marabos.nl/atomics/building-arc An example function demonstrating a potential issue with reference counting. This function continuously downgrades and upgrades an `Arc`, creating scenarios where `Arc` and `Weak` pointers might momentarily not exist, which could lead to race conditions if not handled carefully. ```Rust fn annoying(mut arc: Arc) { loop { let weak = Arc::downgrade(&arc); drop(arc); println!("I have no Arc!"); 1 arc = weak.upgrade().unwrap(); drop(weak); println!("I have no Weak!"); 2 } } ``` -------------------------------- ### Atomic Channel Usage Example (Rust) Source: https://marabos.nl/atomics/building-channels This Rust code demonstrates the practical usage of the atomic channel. It creates a channel, splits it into sender and receiver, spawns a thread to send a message, and then receives and asserts the message in the main thread. It highlights thread synchronization and message passing. ```rust fn main() { let mut channel = Channel::new(); thread::scope(|s| { let (sender, receiver) = channel.split(); let t = thread::current(); s.spawn(move || { sender.send("hello world!"); t.unpark(); }); while !receiver.is_ready() { thread::park(); } assert_eq!(receiver.receive(), "hello world!"); }); } ``` -------------------------------- ### Conceptual Atomic Operation with Detailed Ordering Constraints Source: https://marabos.nl/atomics/memory-ordering A conceptual example showing the complexity of manually specifying memory ordering constraints for an atomic operation. This highlights the need for a more abstract and standardized approach like Rust's `Ordering` enum. ```Pseudocode let x = a.fetch_add(1, Dear compiler and processor, Feel free to reorder this with operations on b, but if there's another thread concurrently executing f, please don't reorder this with operations on c! Also, processor, don't forget to flush your store buffer! If b is zero, though, it doesn't matter. In that case, feel free to do whatever is fastest. Thanks~ <3 ); ``` -------------------------------- ### Rust Prelude for Atomics and Locks Source: https://marabos.nl/atomics/preface A common prelude used in the book's code examples to import necessary items for atomic operations and locks. This saves repetition in individual examples. It includes types from std::cell, std::collections, std::marker, std::mem, std::ops, std::ptr, std::rc, and std::sync. ```rust #[allow(unused)] use std::{ cell::{Cell, RefCell, UnsafeCell}, collections::VecDeque, marker::PhantomData, mem::{ManuallyDrop, MaybeUninit}, ops::{Deref, DerefMut}, ptr::NonNull, rc::Rc, sync::{*}, }; use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering::*}; use std::thread::{self, Thread}; ``` -------------------------------- ### Thread Synchronization with Futex in Rust Source: https://marabos.nl/atomics/os-primitives Demonstrates a practical example of using the custom `wait` and `wake_one` futex functions in Rust to synchronize two threads. One thread modifies an atomic variable and then wakes the main thread, which was waiting on that variable's state. This highlights the atomic nature of futex operations preventing lost signals. ```rust use std::sync::atomic::{AtomicU32, Ordering::Relaxed}; use std::thread; use std::time::Duration; // Assume wait and wake_one functions are defined as in the previous snippet fn main() { let a = AtomicU32::new(0); thread::scope(|s| { s.spawn(|| { thread::sleep(Duration::from_secs(3)); a.store(1, Relaxed); wake_one(&a); }); println!("Waiting..."); while a.load(Relaxed) == 0 { wait(&a, 0); } println!("Done!"); }); } ``` -------------------------------- ### Blocking Channel Usage Example (Rust) Source: https://marabos.nl/atomics/building-channels Provides a `main` function demonstrating the usage of the implemented blocking `Channel`. It uses `thread::scope` to spawn a sender thread that sends a message, and the main thread (acting as receiver) waits for and asserts the received message. ```rust fn main() { let mut channel = Channel::new(); thread::scope(|s| { let (sender, receiver) = channel.split(); s.spawn(move || { sender.send("hello world!"); }); assert_eq!(receiver.receive(), "hello world!"); }); } ``` -------------------------------- ### Rust: Complex Use Case with Release and Acquire Fences Source: https://marabos.nl/atomics/memory-ordering A more elaborate example showcasing the use of release and acquire fences in a multi-threaded context. It involves multiple threads calculating data, storing it, and signaling readiness, with another thread reading the data after synchronization. ```rust use std::sync::atomic::{fence, AtomicBool, Ordering::*}; use std::thread; use std::time::Duration; // Placeholder for some calculation function fn some_calculation(i: usize) -> u64 { i as u64 * 10 } // Static mutable data array, generally unsafe, used here for demonstration static mut DATA: [u64; 10] = [0; 10]; const ATOMIC_FALSE: AtomicBool = AtomicBool::new(false); static READY: [AtomicBool; 10] = [ATOMIC_FALSE; 10]; fn main() { for i in 0..10 { thread::spawn(move || { let data = some_calculation(i); unsafe { DATA[i] = data }; READY[i].store(true, Release); }); } thread::sleep(Duration::from_millis(500)); let mut ready_status = [false; 10]; for i in 0..10 { ready_status[i] = READY[i].load(Relaxed); } if ready_status.iter().any(|&r| r) { fence(Acquire); for i in 0..10 { if ready_status[i] { unsafe { println!("data{} = {}", i, DATA[i]) }; } } } } ``` -------------------------------- ### Rust AtomicUsize Progress Reporting with Thread Parking Source: https://marabos.nl/atomics/atomics Enhances the progress reporting example by using `thread::park_timeout` and `unpark` for more immediate updates and reduced latency. The background thread unparks the main thread after each update, and the main thread uses `park_timeout` to wait efficiently, being woken up by the background thread or by a timeout. This ensures timely status updates and a responsive program. ```rust use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering::Relaxed; use std::thread; use std::time::Duration; fn process_item(i: usize) { // Placeholder for processing an item thread::sleep(Duration::from_millis(50)); } fn main() { let num_done = AtomicUsize::new(0); let main_thread = thread::current(); thread::scope(|s| { // A background thread to process all 100 items. s.spawn(|| { for i in 0..100 { process_item(i); // Assuming this takes some time. num_done.store(i + 1, Relaxed); main_thread.unpark(); // Wake up the main thread. } }); // The main thread shows status updates. loop { let n = num_done.load(Relaxed); if n == 100 { break; } println!("Working.. {n}/100 done"); thread::park_timeout(Duration::from_secs(1)); } }); println!("Done!"); } ``` -------------------------------- ### Returning Values from Threads with Join in Rust Source: https://marabos.nl/atomics/basics Shows how to retrieve a value from a spawned thread in Rust using the `join()` method. The closure's return value is encapsulated in a `Result`, which can be unwrapped to get the computed value. This example calculates the average of a vector of numbers. ```rust use std::thread; use std::iter::FromIterator; fn main() { let numbers = Vec::from_iter(0..=1000); let t = thread::spawn(move || { let len = numbers.len(); if len == 0 { return 0; // Handle empty vector case } let sum = numbers.iter().sum::(); sum / len }); let average = t.join().unwrap(); println!("average: {average}"); } ``` -------------------------------- ### Rust fence(Acquire) to x86-64 Assembly Source: https://marabos.nl/atomics/hardware Demonstrates the compilation of a Rust acquire fence to x86-64 assembly. On x86-64, acquire fences do not generate specific instructions as the architecture inherently provides these semantics. ```rust pub fn a() { fence(Acquire); } ``` ```assembly a: ret ``` -------------------------------- ### ARM64 Atomic Fetch-Add AcqRel Source: https://marabos.nl/atomics/hardware Illustrates the ARM64 assembly for an atomic fetch-and-add operation with Acquire-Release ordering. This involves a loop using 'ldaxr' (load-acquire exclusive) and 'stlxr' (store-release exclusive) instructions to ensure both load and store ordering. ```Rust pub fn a(x: &AtomicI32) { x.fetch_add(10, AcqRel); } ``` ```Assembly a: .L1: ldaxr w8, [x0] 3 add w9, w8, #10 stlxr w10, w9, [x0] 4 cbnz w10, .L1 ret ``` -------------------------------- ### Rust fence(SeqCst) to x86-64 Assembly Source: https://marabos.nl/atomics/hardware Shows the compilation of a Rust sequentially consistent fence to x86-64 assembly. A `SeqCst` fence on x86-64 results in an `mfence` instruction, enforcing a full memory barrier. ```rust pub fn a() { fence(SeqCst); } ``` ```assembly a: mfence ret ``` -------------------------------- ### Rust Function Definition Source: https://marabos.nl/atomics/hardware Defines a simple Rust function `add_ten` that takes a mutable reference to an i32 and increments its value by 10. This function serves as a basic example for analyzing assembly output. ```rust pub fn add_ten(num: &mut i32) { *num += 10; } ``` -------------------------------- ### Rust: Conditional Acquire Fence for Null Pointer Check Source: https://marabos.nl/atomics/memory-ordering Example demonstrating how an acquire fence can be applied conditionally, only when a loaded pointer is not null. This optimizes performance by avoiding acquire ordering when data is not accessed. ```rust use std::sync::atomic::{AtomicPtr, Ordering::*}; use std::ptr; let PTR: AtomicPtr = AtomicPtr::new(ptr::null_mut()); // Using acquire-load let p_acquire = PTR.load(Acquire); if p_acquire.is_null() { println!("no data"); } else { unsafe { println!("data = {}", *p_acquire) }; } // Using conditional acquire fence let p_cond = PTR.load(Relaxed); if p_cond.is_null() { println!("no data"); } else { std::sync::atomic::fence(Acquire); unsafe { println!("data = {}", *p_cond) }; } ``` -------------------------------- ### Compare-Exchange Strong (ARM64 Assembly) Source: https://marabos.nl/atomics/hardware Illustrates the ARM64 assembly for a strong compare-exchange operation, which includes a retry loop for failed attempts. This is achieved using LL/SC instructions. ```Rust pub fn a(x: &AtomicI32) { x.compare_exchange(5, 6, Relaxed, Relaxed); } ``` ```Assembly a: mov w8, #6 .L1: ldxr w9, [x0] cmp w9, #5 b.ne .L2 stxr w9, w8, [x0] cbnz w9, .L1 ret .L2: clrex ret ``` -------------------------------- ### Rust SpinLock Compiler Error Example Source: https://marabos.nl/atomics/building-spinlock Illustrates a compile-time error in Rust when attempting to use a SpinLock guard after it has been explicitly dropped. This demonstrates Rust's safety features in preventing use-after-free or use-after-move errors. ```rust error[E0382]: borrow of moved value: `g` --> src/lib.rs | | drop(g); | - value moved here | g.push(2); | ^^^^^^^^^ value borrowed here after move ``` -------------------------------- ### Rust fence(AcqRel) to ARM64 Assembly Source: https://marabos.nl/atomics/hardware Explains the compilation of a Rust acquire-release fence to ARM64 assembly. On ARM64, `AcqRel` fences compile to `dmb ish`, ensuring both acquire and release semantics. ```rust pub fn a() { fence(AcqRel); } ``` ```assembly a: dmb ish ret ``` -------------------------------- ### Get Mutable Reference Arc::get_mut in Rust Source: https://marabos.nl/atomics/building-arc The `get_mut` method provides mutable access to the data within an `Arc` if and only if there is exactly one strong reference (`alloc_ref_count == 1`) and no weak references. It ensures exclusivity before returning a mutable reference. ```rust impl Arc { … pub fn get_mut(arc: &mut Self) -> Option<&mut T> { if arc.weak.data().alloc_ref_count.load(Relaxed) == 1 { fence(Acquire); // Safety: Nothing else can access the data, since // there's only one Arc, to which we have exclusive access, // and no Weak pointers. let arcdata = unsafe { arc.weak.ptr.as_mut() }; let option = arcdata.data.get_mut(); // We know the data is still available since we // have an Arc to it, so this won't panic. let data = option.as_mut().unwrap(); Some(data) } else { None } } … } ``` -------------------------------- ### Rust Mutex: Increment with Delayed Unlock Source: https://marabos.nl/atomics/basics Illustrates the effect of holding a Mutex lock for an extended period. This example shows how a sleep within the locked section serializes thread execution, increasing total runtime. ```Rust use std::sync::Mutex; use std::time::Duration; fn main() { let n = Mutex::new(0); thread::scope(|s| { for _ in 0..10 { s.spawn(|| { let mut guard = n.lock().unwrap(); for _ in 0..100 { *guard += 1; } thread::sleep(Duration::from_secs(1)); // New! }); } }); assert_eq!(n.into_inner().unwrap(), 1000); } ``` -------------------------------- ### Compare-Exchange Weak (ARM64 Assembly) Source: https://marabos.nl/atomics/hardware Demonstrates the ARM64 assembly generated for a weak compare-exchange operation on an AtomicI32. It uses LL/SC instructions for atomic memory operations. ```Rust pub fn a(x: &AtomicI32) { x.compare_exchange_weak(5, 6, Relaxed, Relaxed); } ``` ```Assembly a: ldxr w8, [x0] cmp w8, #5 b.ne .L1 mov w8, #6 stxr w9, w8, [x0] ret .L1: clrex ret ``` -------------------------------- ### Rust Mutex: Basic Increment Example Source: https://marabos.nl/atomics/basics Demonstrates the basic usage of Rust's Mutex to protect an integer counter incremented by multiple threads. The Mutex ensures atomic operations, preventing race conditions. ```Rust use std::sync::Mutex; fn main() { let n = Mutex::new(0); thread::scope(|s| { for _ in 0..10 { s.spawn(|| { let mut guard = n.lock().unwrap(); for _ in 0..100 { *guard += 1; } }); } }); assert_eq!(n.into_inner().unwrap(), 1000); } ``` -------------------------------- ### x86-64 Atomic Store (Release) Source: https://marabos.nl/atomics/hardware Demonstrates the compiled x86-64 assembly for a Rust atomic store operation with Release memory ordering. On x86-64, this operation is typically a simple move instruction, as the architecture's inherent ordering satisfies Release semantics. ```rust pub fn a(x: &AtomicI32) { x.store(0, Release); } ``` ```assembly a: mov dword ptr [rdi], 0 ret ``` -------------------------------- ### Example Usage of Thread Scope and Channel in Rust Source: https://marabos.nl/atomics/building-channels This Rust code demonstrates the usage of `thread::scope` to manage threads and a channel for inter-thread communication. It sends a message from a spawned thread and receives it in the main thread after ensuring the message is ready. ```rust fn main() { thread::scope(|s| { let (sender, receiver) = channel(); let t = thread::current(); s.spawn(move || { sender.send("hello world!"); t.unpark(); }); while !receiver.is_ready() { thread::park(); } assert_eq!(receiver.receive(), "hello world!"); }); } ``` -------------------------------- ### x86-64 Atomic Fetch-Add (AcqRel) Source: https://marabos.nl/atomics/hardware Illustrates the x86-64 assembly generated for a Rust atomic fetch-and-add operation using AcqRel memory ordering. This operation typically uses the 'lock' prefix to ensure atomicity and ordering guarantees. ```rust pub fn a(x: &AtomicI32) { x.fetch_add(10, AcqRel); } ``` ```assembly a: lock add dword ptr [rdi], 10 ret ``` -------------------------------- ### Leaking MutexGuard to Demonstrate Drop Issue in Rust Source: https://marabos.nl/atomics/os-primitives An example in Rust demonstrating a scenario where a `MutexGuard` is intentionally leaked using `std::mem::forget`. This highlights the potential issue of dropping a `Mutex` while it is still locked, which can lead to undefined behavior in the underlying pthread implementation. ```rust fn main() { let m = Mutex::new(..); let guard = m.lock(); // Lock it .. std::mem::forget(guard); // .. but don't unlock it. } ``` -------------------------------- ### Rust Channel Initialization and Splitting Source: https://marabos.nl/atomics/building-channels Provides the `new` constructor for creating a `Channel` and the `split` method. The `split` method exclusively borrows the channel and returns `Sender` and `Receiver` instances that share borrows of the channel, ensuring safe concurrent access control. ```rust impl Channel { pub const fn new() -> Self { Self { message: UnsafeCell::new(MaybeUninit::uninit()), ready: AtomicBool::new(false), } } pub fn split<'a>(&'a mut self) -> (Sender<'a, T>, Receiver<'a, T>) { *self = Self::new(); (Sender { channel: self }, Receiver { channel: self }) } } ``` -------------------------------- ### Arc::new Constructor Source: https://marabos.nl/atomics/building-arc The `new` function creates a new `Arc` instance. It allocates `ArcData` using `Box::new`, leaks the box to get a stable pointer with `Box::leak`, and initializes the `ref_count` to 1. Finally, it converts the raw pointer into a `NonNull` pointer. ```rust impl Arc { pub fn new(data: T) -> Arc { Arc { ptr: NonNull::from(Box::leak(Box::new(ArcData { ref_count: AtomicUsize::new(1), data, }))), } } … } ``` -------------------------------- ### x86-64 Assembly for `add_ten` Function Source: https://marabos.nl/atomics/hardware Presents the assembly output for the `add_ten` Rust function compiled for x86-64 architecture with optimizations. It highlights the conciseness of the CISC architecture, where a single instruction can perform the increment operation. ```assembly add_ten: add dword ptr [rdi], 10 ret ``` -------------------------------- ### Demonstrating fetch_add Return Value in Rust Source: https://marabos.nl/atomics/atomics This code demonstrates the usage of `fetch_add` on an `AtomicI32`. It shows that `fetch_add` returns the value of the atomic variable *before* the addition operation. The example verifies this behavior by asserting the returned value and the final loaded value. ```rust use std::sync::atomic::AtomicI32; let a = AtomicI32::new(100); let b = a.fetch_add(23, Relaxed); let c = a.load(Relaxed); assert_eq!(b, 100); assert_eq!(c, 123); ``` -------------------------------- ### Rust fence(AcqRel) to x86-64 Assembly Source: https://marabos.nl/atomics/hardware Presents the compilation of a Rust acquire-release fence to x86-64 assembly. Like other fences on x86-64, `AcqRel` does not generate a specific instruction. ```rust pub fn a() { fence(AcqRel); } ``` ```assembly a: ret ``` -------------------------------- ### Rust fence(SeqCst) to ARM64 Assembly Source: https://marabos.nl/atomics/hardware Details the compilation of a Rust sequentially consistent fence to ARM64 assembly. On ARM64, `SeqCst` fences are compiled to `dmb ish`, similar to release fences, providing strong memory ordering guarantees. ```rust pub fn a() { fence(SeqCst); } ``` ```assembly a: dmb ish ret ``` -------------------------------- ### Channel Split Method for Blocking Channel Initialization (Rust) Source: https://marabos.nl/atomics/building-channels Modifies the `Channel::split` method to initialize the new fields in `Sender` and `Receiver`. It creates a `Sender` with the current thread's handle and a `Receiver` marked as non-sendable. This setup is essential for the blocking mechanism. ```rust pub fn split<'a>(&'a mut self) -> (Sender<'a, T>, Receiver<'a, T>) { *self = Self::new(); ( Sender { channel: self, receiving_thread: thread::current(), // New! }, Receiver { channel: self, _no_send: PhantomData, // New! } ) } ``` -------------------------------- ### Rust Condvar Example: Producer-Consumer Queue Synchronization Source: https://marabos.nl/atomics/basics Demonstrates the use of `std::sync::Condvar` to synchronize a producer-consumer pattern with a shared queue. The consumer thread waits on the `not_empty` condition variable when the queue is empty, and the producer notifies it when an item is added. ```rust use std::collections::VecDeque; use std::sync::{Condvar, Mutex}; use std::thread; use std::time::Duration; let queue = Mutex::new(VecDeque::new()); let not_empty = Condvar::new(); thread::scope(|s| { s.spawn(|| { loop { let mut q = queue.lock().unwrap(); let item = loop { if let Some(item) = q.pop_front() { break item; } else { // Atomically unlocks the mutex and waits for a notification. // Relocks the mutex before returning. q = not_empty.wait(q).unwrap(); } }; drop(q); // Drop the guard before processing the item dbg!(item); } }); for i in 0.. { queue.lock().unwrap().push_back(i); // Notifies one waiting thread that the condition might be met. not_empty.notify_one(); thread::sleep(Duration::from_secs(1)); } }); ``` -------------------------------- ### x86-64 Atomic Store (SeqCst) Source: https://marabos.nl/atomics/hardware Presents the compiled x86-64 assembly for a Rust atomic store operation with SeqCst (Sequentially Consistent) memory ordering. This ordering requires a stronger instruction, like 'xchg', to prevent reordering and ensure global consistency. ```rust pub fn a(x: &AtomicI32) { x.store(0, SeqCst); } ``` ```assembly a: xor eax, eax xchg dword ptr [rdi], eax ret ``` -------------------------------- ### Using Cell for Mutable Operations in Rust Source: https://marabos.nl/atomics/basics Demonstrates how `std::cell::Cell` enables mutation through shared references in a single-threaded context. It allows getting and setting values, but requires taking and replacing the entire value for complex types like `Vec`. ```rust use std::cell::Cell; fn f(a: &Cell, b: &Cell) { let before = a.get(); b.set(b.get() + 1); let after = a.get(); if before != after { x(); // might happen } } ``` ```rust use std::cell::Cell; fn f(v: &Cell>) { let mut v2 = v.take(); // Replaces the contents of the Cell with an empty Vec v2.push(1); v.set(v2); // Put the modified Vec back } ```