Rust threading: Difference between revisions

From wikinotes
No edit summary
Line 72: Line 72:
</syntaxhighlight>
</syntaxhighlight>
</blockquote><!-- mspc::channel -->
</blockquote><!-- mspc::channel -->
== mutexes ==
<blockquote>
rust mutexes are a bit unique, in that they wrap the object you are protecting.<br>
when the object falls out of scope, the lock is released.
<syntaxhighlight lang="rust">
let m = Mutex::new(5);
{
    let mut num = m.lock().unwrap();
    *num = 6;
} // lock is released
</syntaxhighlight>
If a mutex is shared between threads, wrap it in <code>Arc<T></code> so the reference is not deleted.<br>
This behaves like a <code>Rc<T></code> for threads (see [[rust pointers]] for more details).
<syntaxhighlight lang="rust">
use std::thread;
use std::sync::Arc;
use std::sync::Mutex;
fn main() {
    let src_counter_lock = Arc::new(Mutex::new(0));
    let counter_lock = Arc::clone(&src_counter_lock);
    let spawn_thread = move|| {
        let counter_lock = Arc::clone(&src_counter_lock);
        thread::spawn(move|| {
            let mut count = counter_lock.lock().unwrap();
            *count += 1;
        })
    };
    (1..=3)
        .map(|_| spawn_thread())
        .for_each(|h| h.join().expect("unable to join thread"));
    println!("count: {}", counter_lock.lock().unwrap());
}
</syntaxhighlight>
</blockquote><!-- mutexes -->
</blockquote><!-- Synchronization -->
</blockquote><!-- Synchronization -->

Revision as of 04:47, 10 February 2023

Documentation

rust book: concurrency https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html
std::sync builtin synchronization primitives

Basics

Since rust already manages ownership semantics, you don't really need to deal with thread affinity.
Simply pass a closure to a thread, move any params to it, and call it a day.

thead without outer scope access

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..=5 {
            println!("step {}/5..", i);
            thread::sleep(Duration::from_secs(1));
        }
    });
    handle.join().expect("unable to join thread");
}

thread that moves outer-scope into thread's closure

use std::thread;
use std::time::Duration;

fn main() {
    let name = String::from("alex");
    let handle = thread::spawn(move || {
        for _ in 1..=5 {
            println!("hi {}!", name);
            thread::sleep(Duration::from_secs(1));
        }
    });
    handle.join().expect("unable to join thread");
}

Synchronization

mspc::channel

multiple producer, single consumer, FIFO queue.
do work in threads, have eventloop in main thread.

use std::thread;
use std::sync::mpsc;

fn main() {
    let (tx, rx) = mpsc::channel();

    let handle = thread::spawn(move|| {
        tx.send(123).unwrap();
    });

    println!("hello to {}", rx.recv().unwrap());
    handle.join().expect("unable to join thread");
}

mutexes

rust mutexes are a bit unique, in that they wrap the object you are protecting.
when the object falls out of scope, the lock is released.

let m = Mutex::new(5);
{
    let mut num = m.lock().unwrap();
    *num = 6;
} // lock is released

If a mutex is shared between threads, wrap it in Arc<T> so the reference is not deleted.
This behaves like a Rc<T> for threads (see rust pointers for more details).

use std::thread;
use std::sync::Arc;
use std::sync::Mutex;

fn main() {
    let src_counter_lock = Arc::new(Mutex::new(0));
    let counter_lock = Arc::clone(&src_counter_lock);

    let spawn_thread = move|| {
        let counter_lock = Arc::clone(&src_counter_lock);
        thread::spawn(move|| {
            let mut count = counter_lock.lock().unwrap();
            *count += 1;
        })
    };

    (1..=3)
        .map(|_| spawn_thread())
        .for_each(|h| h.join().expect("unable to join thread"));

    println!("count: {}", counter_lock.lock().unwrap());
}