rust-atomics-and-locks

repository·main·Indexed 21 days ago

https://github.com/m-ou-se/rust-atomics-and-locks

A collection of code examples and data structure implementations accompanying the book 'Rust Atomics and Locks' by Mara Bos. The repository provides practical demonstrations of low-level concurrency, atomics, and memory ordering (Relaxed, Release/Acquire, SeqCst), as well as custom implementations of synchronization primitives including Spin Locks, Channels, Arc/Weak pointers, Mutex, Condvar, and RwLock.

Tokens
4.3K
Snippets
13
Records
40
Agent score
81%

What's inside rust-atomics-and-locks

  1. Locate code examples and data structure implementations

    main

    This repository serves as a code companion for the book Rust Atomics and Locks by Mara Bos. Depending on what you are looking for, the code is organized into two main directories:

    • Examples: Contains runnable code examples for Chapters 1, 2, 3, and 8. These are located in the examples/ directory.
    • Data Structures: Contains the implementations of custom data structures for Chapters 4, 5, 6, and 9. These are located in the src/ directory.
  2. Explore the Rust Atomics and Locks modules

    main

    This library provides educational implementations of concurrency primitives. You can access the following modules to study specific implementations:

    • ch4_spin_lock: Implementation of a Spin Lock.
    • ch5_channels: Implementation of communication channels.
    • ch6_arc: Implementation of an Atomic Reference Counted (Arc) pointer.
    • ch9_locks: Implementation of various lock primitives.
  3. Explore custom data structure implementations

    main

    The src/ directory contains the source code for building complex synchronization primitives from scratch:

    • Spin Locks (Chapter 4): Minimal, unsafe, and guard-based implementations.
    • Channels (Chapter 5): Implementations ranging from simple to unsafe, including checks for single-atomic usage, type definitions, and blocking logic.
    • Arc (Chapter 6): Implementations of Atomic Reference Counting, including Weak pointer support and optimized versions.
    • Locks (Chapter 9): Custom implementations of Mutex, Condvar, and RwLock (Read-Write Lock).
  4. Explore Rust Concurrency basics and Atomics examples

    main

    The examples/ directory contains practical demonstrations of concurrency primitives and atomic operations:

    Chapter 1: Basics of Rust Concurrency

    Covers fundamental concepts like spawn, join, scoped threads, Rc, Cell, RefCell, Mutex, Condvar, and thread parking.

    Chapter 2: Atomics

    Demonstrates various atomic patterns including stop flags, progress reporting, lazy initialization, fetch_add, ID allocation, and compare_exchange loops.

    Chapter 3: Memory Ordering

    Explores different memory ordering models such as Relaxed, Release/Acquire, SeqCst (Sequentially Consistent), and the use of memory fences.

  5. Example: Single-message communication between threads

    main

    This example demonstrates how to use Channel<T> to pass a string from a spawned thread to the main thread using thread::park and thread::unpark for synchronization.

    use std::thread;
    let channel = Channel::new();
    let t = thread::current();
    thread::scope(|s| {
        s.spawn(|| {
            channel.send("hello world!");
            t.unpark();
        });
        while !channel.is_ready() {
            thread::park();
        }
        assert_eq!(channel.receive(), "hello world!");
    });
  6. Example: Basic channel usage with threads

    main

    This example demonstrates how to use the channel API to pass a string from a spawned thread to the main thread using thread::park and thread::unpark for synchronization.

    use std::thread;
    
    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!");
    });
  7. Use the Channel<T> primitive for unsafe message passing

    main

    The Channel<T> struct provides a low-level, unsafe mechanism for sending a single message between threads. It uses an UnsafeCell to hold a MaybeUninit<T> and an AtomicBool to signal readiness.

    Safety Requirements:

    • send must be called exactly once.
    • receive must be called exactly once, and only after is_ready() returns true.
    • The type T must implement Send to allow the Channel to be Sync.
  8. Use the Mutex<T> implementation for mutual exclusion

    main

    The Mutex<T> provides a way to share data between threads by ensuring only one thread can access the underlying value at a time.

    To use it:

    1. Initialize the mutex using Mutex::new(value).
    2. Acquire the lock by calling .lock(), which returns a MutexGuard<T>.
    3. Access or modify the data through the guard using standard dereferencing (*guard or calling methods on the guard).
    4. The lock is automatically released when the MutexGuard is dropped (goes out of scope).

    This implementation uses atomic_wait to park threads when the lock is contested, making it more efficient than a pure spinlock.

  9. Use Weak<T> to prevent reference cycles

    main

    Weak<T> is a non-owning reference to data managed by an Arc<T>. It is used to prevent reference cycles that would otherwise cause memory leaks. Unlike Arc, a Weak pointer does not prevent the underlying data from being dropped.

    Key capabilities:

    • Downgrade: Convert an Arc<T> into a Weak<T> using arc.downgrade().
    • Upgrade: Attempt to turn a Weak<T> back into an Arc<T> using .upgrade(). This returns Some(Arc<T>) if the data still exists, or None if the data has already been dropped.
  10. Use Arc and Weak pointers for shared ownership

    main

    The Arc<T> (Atomically Reference Counted) and Weak<T> types provide a mechanism for shared ownership of data. Arc<T> maintains a strong reference count to keep the data alive, while Weak<T> provides a non-owning reference that can be temporarily upgraded back into an Arc<T> if the data still exists.

    Key Operations

    • Creation: Use Arc::new(data) to create a new atomically reference-counted pointer.
    • Downgrading: Use Arc::downgrade(&arc) to create a Weak<T> pointer from an existing Arc<T>. This does not increase the strong reference count.
    • Upgrading: Use Weak::upgrade() to attempt to convert a Weak<T> back into an Arc<T>. This returns Some(Arc<T>) if the data is still alive, or None if the strong reference count has reached zero.
    • Exclusive Access: Use Arc::get_mut(&mut arc) to attempt to obtain a mutable reference to the underlying data. This only succeeds if there are no other Arc or Weak pointers currently pointing to the data (i.e., the allocation reference count is exactly 1).
  11. Use RwLock for shared read access and exclusive write access

    main

    The RwLock<T> provides a synchronization primitive that allows multiple readers to access data simultaneously, but requires exclusive access for writers.

    • Use new(value: T) to initialize the lock with a value.
    • Use read() to acquire a ReadGuard<T>. This method will block if a writer currently holds the lock.
    • Use write() to acquire a WriteGuard<T>. This method will block if there are active readers or another writer holding the lock.

    RwLock<T> implements Sync if T is both Send and Sync.