Last active
January 16, 2025 19:21
-
-
Save caquillo07/9524ec11ee9d0f80896fb2f04cac6c3e to your computer and use it in GitHub Desktop.
Rust simple thread + channel example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "rust_threads_mutex" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
# annoyingly, Rust has no built-in random generator... | |
rand = "0.8.5" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate rand; | |
use std::sync::mpsc::channel; | |
use std::sync::{Arc, Mutex}; | |
use std::{thread, time}; | |
use rand::Rng; | |
// docs on mutex: | |
// https://doc.rust-lang.org/std/sync/struct.Mutex.html | |
struct Counter { | |
num: u64, | |
} | |
impl Counter { | |
fn default() -> Counter { | |
return Counter { num: 0 }; | |
} | |
fn inc(&mut self, amount: u64) { | |
self.num += amount; | |
} | |
} | |
fn main() { | |
println!("Hello, world!"); | |
let global_counter: Arc<Mutex<Counter>> = Arc::new(Mutex::new(Counter::default())); | |
// let mut counter = global_counter.lock().unwrap(); | |
// we have to clone it so Rust's Arc can keep track of the references | |
let counter_clone = Arc::clone(&global_counter); | |
// tx = transfer channel | |
// rx = receiving channel | |
let (tx, rx) = channel(); | |
// here 'move' means the thread is now the owner of the memory for `counter_clone`, | |
// and it will 'free' it, so it cannot be used again. | |
// || {} is a closure that takes no parameters ( the `| |` ), and | |
// returns nothing (lacks the `->` ) | |
println!("kicking off thread 1"); | |
thread::spawn(move || { | |
println!("thread 1 is starting"); | |
// do some "work" | |
let secs_to_wait: u64 = rand::thread_rng().gen_range(0..4); | |
let secs = time::Duration::from_secs(secs_to_wait); | |
thread::sleep(secs); | |
// let mut counter = global_counter.lock().unwrap(); // uncomment to see error, why you have to move | |
let mut counter = counter_clone.lock().unwrap(); | |
counter.inc(secs_to_wait); | |
// signaling we are done | |
tx.send(secs_to_wait).unwrap(); | |
println!("thread 1 is done"); | |
// when counter goes out of scope, Rust implicitly calls drop() on the counter | |
// which releases the memory, and releases the lock on the mutex. | |
// | |
// std::mem::drop(counter); | |
}); | |
// let counter = counter_clone.lock().unwrap(); // fails compilation if uncommented with line 51 | |
// println!("counter {}", counter.num); | |
// we are doing the second thread here. Rust allows for variable shadowing like this below, | |
// so we can reuse the names, but we changed it for clarity | |
let counter_clone2 = Arc::clone(&global_counter); | |
let (tx2, rx2) = channel(); | |
println!("kicking off thread 2"); | |
thread::spawn(move || { | |
println!("thread 2 is starting"); | |
// do some "work" | |
let secs_to_wait: u64 = rand::thread_rng().gen_range(0..4); | |
let secs = time::Duration::from_secs(secs_to_wait); | |
thread::sleep(secs); | |
let mut counter = counter_clone2.lock().unwrap(); | |
counter.inc(secs_to_wait); | |
// signaling we are done. | |
// this is only needed if we need to know the work is done before the thread exists. | |
// This is useful when you have a worker threader that loops and is continuously sending | |
// messages, or if we need to some other expensive thing that the main thread does not care about. | |
tx2.send(secs_to_wait).unwrap(); | |
println!("thread 2 is done"); | |
// when counter goes out of scope, Rust implicitly recursively calls drop() on the counter | |
// which releases the memory created by the Arc, decrements the Arc internal counter, and releases | |
// the lock on the mutex. | |
// | |
// if you wanted to do it manually for some reason, which you shouldn't | |
// unless you have a compelling reason to | |
// std::mem::drop(counter); | |
}); | |
println!("waiting for threads..."); | |
// we have two channels, one per thread, we have to wait for both to finish; | |
let thread1_secs_ran = rx.recv().unwrap(); | |
println!("thread 1 took {thread1_secs_ran} seconds to run"); | |
let thread2_secs_ran = rx2.recv().unwrap(); | |
println!("thread 2 took {thread2_secs_ran} seconds to run"); | |
// checking the global counter to make sure it is what we expect | |
let counter = global_counter.lock().unwrap(); | |
assert_eq!(counter.num, thread1_secs_ran + thread2_secs_ran); | |
println!("all threads done, exiting with counter as {}", counter.num); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment