Rosarioyui's Technical Blog

website·Indexed 29 days ago

https://rosarioyui.github.io

A collection of technical posts and notes, including discussions on Rust concurrency, the 1:1 threading model, and the use of std::thread and Mutex.

Tokens
1.3K
Snippets
6
Records
7
Agent score
25%

What's inside Rosarioyui Blog

  1. Manage shared state with `std::sync::Mutex`

    A Mutex (Mutual Exclusion) ensures that only one thread can access data at a time.

    • lock(): Blocks the current thread until the lock is acquired. Returns a MutexGuard (a smart pointer) that provides mutable access to the inner data via Deref and Drop.
    • The lock is automatically released when the MutexGuard goes out of scope.
    • To share a Mutex across multiple threads, it must be wrapped in an atomically reference-counted pointer (Arc), as Rc is not thread-safe (does not implement Send).
    use std::sync::Mutex;
    
    fn main() {
        let m = Mutex::new(5);
    
        {
            // lock() returns a MutexGuard
            let mut num = m.lock().unwrap();
            *num = 6;
        } // lock is released here
    
        println!("m = {m:?}");
    }
  2. Use the `move` keyword with `thread::spawn`

    When spawning a thread, the closure must have a 'static lifetime because the compiler cannot guarantee how long the spawned thread will run. If the closure captures variables from the environment by reference, it may lead to invalid references if the original owner is dropped. Use the move keyword to force the closure to take ownership of the captured variables.

    Note: If the captured variable implements Copy (like i32), the value is copied into the thread, and the original variable remains usable in the main thread.

    use std::thread;
    
    fn main() {
        let v = vec![1, 2, 3];
    
        // Use 'move' to transfer ownership of v into the thread
        let handle = thread::spawn(move || {
            println!("Here's a vector: {v:?}");
        });
    
        handle.join().unwrap();
    }
  3. Spawn and manage basic threads in Rust

    Rust uses a 1:1 threading model where one language thread equals one system thread. Use std::thread::spawn to create a new thread. Note that when the main thread finishes, all spawned threads are shut down regardless of whether they have finished executing. Use thread::sleep to pause a thread for a specific duration, though this does not guarantee a thread switch.
    use std::thread;
    use std::time::Duration;
    
    fn main() {
        thread::spawn(|| {
            for i in 1..10 {
                println!("hi number {i} from the spawned thread!");
                thread::sleep(Duration::from_millis(1));
            }
        });
    
        for i in 1..5 {
            println!("hi number {i} from the main thread!");
            thread::sleep(Duration::from_millis(1));
        }
    }
  4. Implement shared-state concurrency using Arc and Mutex

    To share mutable data across multiple threads in Rust, wrap the data in a Mutex<T> for mutual exclusion (internal mutability) and then wrap that in an Arc<T> (Atomic Reference Counted) to allow multiple owners across threads. Rc<T> cannot be used here because it is not thread-safe.
    use std::sync::{Arc, Mutex};
    use std::thread;
    
    fn main() {
        // Arc allows multiple ownership, Mutex allows safe mutation
        let counter = Arc::new(Mutex::new(0));
        let mut handles = vec![];
    
        for _ in 0..10 {
            let counter = Arc::clone(&counter);
            let handle = thread::spawn(move || {
                // Lock the mutex to get a mutable reference to the data
                let mut num = counter.lock().unwrap();
                *num += 1;
            });
            handles.push(handle);
        }
    
        for handle in handles {
            handle.join().unwrap();
        }
    
        println!("Result: {}", *counter.lock().unwrap());
    }
  5. Implement message passing using `std::sync::mpsc`

    Rust provides mpsc (Multiple Producer, Single Consumer) channels for inter-thread communication.

    • tx.send(val): Sends a value. Returns Err if the receiver has been dropped.
    • rx.recv(): Blocks the current thread until a value is received or the channel closes (returns Err).
    • rx.try_recv(): Non-blocking attempt to receive a value.
    • rx as an iterator: You can iterate over rx to receive multiple messages; the loop ends when all senders are dropped.
    • Multiple Producers: Use tx.clone() to create additional senders for the same channel.
    use std::sync::mpsc;
    use std::thread;
    
    fn main() {
        let (tx, rx) = mpsc::channel();
    
        // Producer 1
        let tx1 = tx.clone();
        thread::spawn(move || {
            tx1.send(String::from("hi")).unwrap();
        });
    
        // Producer 2
        thread::spawn(move || {
            tx.send(String::from("hello")).unwrap();
        });
    
        // Consumer: iterate over rx
        for received in rx {
            println!("Got: {received}");
        }
    }
  6. Understand the Sync marker trait

    The Sync trait is a marker trait indicating that a type is safe to be referenced by multiple threads simultaneously. A type T is Sync if and only if &T (a reference to T) is Send.

    Comparison of common types:

    • Rc<T>: Neither Send nor Sync.
    • RefCell<T> / Cell<T>: Send but NOT Sync (runtime borrow checking is not thread-safe).
    • Mutex<T>: Both Send and Sync (the thread-safe alternative to RefCell).
    • MutexGuard<'a, T>: Sync but NOT Send.
  7. Understand the Send marker trait

    The Send trait is a marker trait that indicates a type's ownership can be safely transferred between threads. Most primitive types are Send. A struct is automatically Send if all of its fields are Send.

    Exceptions: Rc<T> and raw pointers are not Send because they are not thread-safe.

    struct Point {
        x: i32, // i32 is Send
        y: i32, // i32 is Send
    }
    // Point is automatically Send because all fields are Send.