Skip to content

Instantly share code, notes, and snippets.

@caquillo07
Last active January 16, 2025 19:21
Show Gist options
  • Save caquillo07/9524ec11ee9d0f80896fb2f04cac6c3e to your computer and use it in GitHub Desktop.
Save caquillo07/9524ec11ee9d0f80896fb2f04cac6c3e to your computer and use it in GitHub Desktop.
Rust simple thread + channel example
[package]
name = "rust_threads_mutex"
version = "0.1.0"
edition = "2021"
[dependencies]
# annoyingly, Rust has no built-in random generator...
rand = "0.8.5"
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