The Dreaded
Threa[td]s
By Mario A. Santini
Mario A. Santini
a Software Developer
in Telco.
OS, Processes, Threads
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
OS
P1 P2 P3 P4 P5
Threads: you can have many
...but only a number equal or less than the available CPU
cores or CPU Threads will actually run in parallel. The others
will sleep until a spot appears.
...but each thread consume resources as it has his own
memory, so not so many you can have after all.
...but writing code that is ment to be executed from multiple
threads can be very hard to write and to be understood.
...but threads can slow you down much faster than what they
can do to spid up your program ...
Threads: how can Rust help you
...but only a number equal or less than the available CPU cores or CPU
Threads will actually run in parallel. The others will sleep until a spot
appears.
...but each thread consume resources as it has his own memory, so not so
many you can have after all.
...but writing code that is ment to be executed from multiple
threads can be very hard to write and to be understood.
...but threads can slow you down much faster than what they can do to
spid up your program ...
Threads in Rust
use std::thread::Thread;
pub struct Thread { /* private fields */ }
pub fn id(&self) -> ThreadId
pub fn name(&self) -> Option<&str>
pub fn unpark(&self)
Spawn a Thread
use std::thread::spawn;
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
To Run on Multiple Thread
use std::marker::Send;
pub unsafe auto trait Send { }
...you have to be send(able).
...Send is a Trait auto-implemented from the
compiler on all types that are considered safe to
be send throught threads.
...an example of a non-Send type is std::rc::Rc
How to Handle a Thread
use std::thread::JoinHandle;
pub struct JoinHandle<T>(_);
pub fn is_finished(&self) -> bool
pub fn join(self) -> Result<T>
pub fn thread(&self) -> &Thread
Some Thread Utils
use std::thread::*;
pub fn current() -> Thread
pub fn sleep(dur: Duration)
pub fn park()
What You Can “Send” to a Thread
use std::thread::spawn;
fn main() {
let v = "Hello SoCraTeN".to_string();
let t = spawn(move || {
println!("{v}");
});
match t.join() {
Ok(_) => println!("Done!"),
Err(e) => eprintln!("Something failed {:?}", e),
}
}
What You Can’t “Send” to a Thread
use std::thread::spawn;
fn main() {
let v = "Hello SoCraTeN".to_string(); // This is a String!
let mut ts = vec![];
for n in 0..5 {
let t = spawn(move || {
println!("{v} {n}");
});
ts.push(t);
}
match ts
.into_iter()
.map(|t| t.join())
.fold(Ok(()), |a, r| {
match r {
Ok(_) => a,
Err(e) => Err(format!("Something failed {:?}", e)),
}
}) {
Ok(_) => println!("Done!"),
Err(e) => eprintln!("{:?}", e),
}
}
The Fix is Simple, but...
use std::thread::spawn;
fn main() {
let v: &’static str = "Hello SoCraTeN"; // The reference is cloned and then moved!
let mut ts = vec![];
for n in 0..5 {
let t = spawn(move || {
println!("{v} {n}");
});
ts.push(t);
}
match ts
.into_iter()
.map(|t| t.join())
.fold(Ok(()), |a, r| {
match r {
Ok(_) => a,
Err(e) => Err(format!("Something failed {:?}", e)),
}
}) {
Ok(_) => println!("Done!"),
Err(e) => eprintln!("{:?}", e),
}
}
Another Possible Fix
use std::thread::spawn;
fn main() {
let v: String = "Hello SoCraTeN".to_string();
let mut ts = vec![];
for n in 0..5 {
let v = v.to_string() // String is cloned each run now!
let t = spawn(move || {
println!("{v} {n}");
});
ts.push(t);
}
match ts
.into_iter()
.map(|t| t.join())
.fold(Ok(()), |a, r| {
match r {
Ok(_) => a,
Err(e) => Err(format!("Something failed {:?}", e)),
}
}) {
Ok(_) => println!("Done!"),
Err(e) => eprintln!("{:?}", e),
}
}
You Can’t Use Rc so Just Use Arc
use std::sync::Arc;
pub struct Arc<T>
where
T: ?Sized,
{ /* private fields */ }
impl<T> Clone for Arc<T>
where
T: ?Sized,
impl<T> Send for Arc<T>
where
T: Sync + Send + ?Sized,
Arc an Example
use std::sync::Arc;
use std::thread::spawn;
fn main() {
let v = Arc::new(String::from("Hello SoCraTeN"));
let mut ts = vec![];
for n in 0..5 {
let v = v.clone();
let t = spawn(move || {
println!("{v} {n}");
});
ts.push(t);
}
match ts
.into_iter()
.map(|t| t.join())
.fold(Ok(()), |a, r| {
match r {
Ok(_) => a,
Err(e) => Err(format!("Something failed {:?}", e)),
}
}) {
Ok(_) => println!("Done!"),
Err(e) => eprintln!("{:?}", e),
}
}
Yes, But How I Change Stuff?
use std::marker::Sync;
pub unsafe auto trait Sync { }
The precise definition is: a type T is Sync if &T is Send.
In other words, if there is no possibility of undefined
behavior (including data races) when passing &T
references between threads.
[...cut...]
Types that are not Sync are those that have "interior
mutability" in a non-thread-safe form, such as cell::Cell
and cell::RefCell.
https://doc.rust-lang.org/std/marker/trait.Sync.html
Change State Through Threads
use std::sync::{Arc, Mutex};
use std::thread::spawn;
fn main() {
let v = Arc::new(
Mutex::new(
String::from("Hello SoCraTeN")
)
);
let mut ts = vec![];
for n in 0..5 {
let v = v.clone();
let t = spawn(move || {
let mut string = v.lock().unwrap();
string.insert_str(6, &format!("{n} "));
println!("{string}");
});
ts.push(t);
}
/** match… */
}
This is possible,
but not desirable!
Most of the time
The Atomics
Atomic types provide primitive shared-memory
communication between threads, and are the
building blocks of other concurrent types.
https://doc.rust-lang.org/std/sync/atomic/index.html
An Example of Atomics
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{hint, thread};
fn main() {
let spinlock = Arc::new(AtomicUsize::new(1));
let spinlock_clone = Arc::clone(&spinlock);
let thread = thread::spawn(move|| {
spinlock_clone.store(0, Ordering::SeqCst);
});
// Wait for the other thread to release the lock
while spinlock.load(Ordering::SeqCst) != 0 {
hint::spin_loop();
}
if let Err(panic) = thread.join() {
println!("Thread had an error: {panic:?}");
}
}
https://doc.rust-lang.org/std/sync/atomic/index.html
Atomics Ordering
pub enum Ordering {
Relaxed,
Release,
Acquire,
AcqRel,
SeqCst,
}
Other Locks: Barrier
use std::sync::{Arc, Barrier};
use std::thread;
fn main() {
let mut handles = Vec::with_capacity(10);
let barrier = Arc::new(Barrier::new(10));
for _ in 0..10 {
let c = Arc::clone(&barrier);
// The same messages will be printed together.
// You will NOT see any interleaving.
handles.push(thread::spawn(move|| {
println!("before wait");
c.wait();
println!("after wait");
}));
}
// Wait for other threads to finish.
for handle in handles {
handle.join().unwrap();
}
}
https://doc.rust-lang.org/std/sync/struct.Barrier.html
Other Locks: Condvar
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
fn main() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = Arc::clone(&pair);
// Inside of our lock, spawn a new thread,
// and then wait for it to start.
thread::spawn(move|| {
let (lock, cvar) = &*pair2;
let mut started = lock.lock().unwrap();
*started = true;
// We notify the condvar that the value has changed.
cvar.notify_one();
});
// Wait for the thread to start up.
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
}
https://doc.rust-lang.org/std/sync/struct.Condvar.html
Channels MPSC
In Rust Standard Library we just have:
Multiple Producers Single Consumer channels!
pub struct Sender<T> { /* private fields */ }
impl Clone, Sync
pub struct SyncSender<T> { /* private fields */ }
impl Sync
pub struct Receiver<T> { /* private fields */ }
impl Send
An Example of Channels
use std::thread;
use std::sync::mpsc::channel;
fn main() {
// Create a simple streaming channel
let (tx, rx) = channel();
thread::spawn(move || {
tx.send(10).unwrap();
});
assert_eq!(rx.recv().unwrap(), 10);
}
References
●
Rust book: The Rust Programming Language
– (https://doc.rust-lang.org/book/)
●
Rustonomincon
– (https://doc.rust-lang.org/nomicon/)
●
Crossbeam
– (https://github.com/crossbeam-rs/crossbeam)
●
Jon Gjengset YouTube channel
– (https://www.youtube.com/c/JonGjengset)
●
Rust Playground
– (https://play.rust-lang.org/)
Thank You!
Have any question?
Please go ahead! :)

Rust_Threads.pdf

  • 1.
  • 2.
    Mario A. Santini aSoftware Developer in Telco.
  • 3.
  • 4.
    Threads: you canhave many ...but only a number equal or less than the available CPU cores or CPU Threads will actually run in parallel. The others will sleep until a spot appears. ...but each thread consume resources as it has his own memory, so not so many you can have after all. ...but writing code that is ment to be executed from multiple threads can be very hard to write and to be understood. ...but threads can slow you down much faster than what they can do to spid up your program ...
  • 5.
    Threads: how canRust help you ...but only a number equal or less than the available CPU cores or CPU Threads will actually run in parallel. The others will sleep until a spot appears. ...but each thread consume resources as it has his own memory, so not so many you can have after all. ...but writing code that is ment to be executed from multiple threads can be very hard to write and to be understood. ...but threads can slow you down much faster than what they can do to spid up your program ...
  • 6.
    Threads in Rust usestd::thread::Thread; pub struct Thread { /* private fields */ } pub fn id(&self) -> ThreadId pub fn name(&self) -> Option<&str> pub fn unpark(&self)
  • 7.
    Spawn a Thread usestd::thread::spawn; pub fn spawn<F, T>(f: F) -> JoinHandle<T> where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static,
  • 8.
    To Run onMultiple Thread use std::marker::Send; pub unsafe auto trait Send { } ...you have to be send(able). ...Send is a Trait auto-implemented from the compiler on all types that are considered safe to be send throught threads. ...an example of a non-Send type is std::rc::Rc
  • 9.
    How to Handlea Thread use std::thread::JoinHandle; pub struct JoinHandle<T>(_); pub fn is_finished(&self) -> bool pub fn join(self) -> Result<T> pub fn thread(&self) -> &Thread
  • 10.
    Some Thread Utils usestd::thread::*; pub fn current() -> Thread pub fn sleep(dur: Duration) pub fn park()
  • 11.
    What You Can“Send” to a Thread use std::thread::spawn; fn main() { let v = "Hello SoCraTeN".to_string(); let t = spawn(move || { println!("{v}"); }); match t.join() { Ok(_) => println!("Done!"), Err(e) => eprintln!("Something failed {:?}", e), } }
  • 12.
    What You Can’t“Send” to a Thread use std::thread::spawn; fn main() { let v = "Hello SoCraTeN".to_string(); // This is a String! let mut ts = vec![]; for n in 0..5 { let t = spawn(move || { println!("{v} {n}"); }); ts.push(t); } match ts .into_iter() .map(|t| t.join()) .fold(Ok(()), |a, r| { match r { Ok(_) => a, Err(e) => Err(format!("Something failed {:?}", e)), } }) { Ok(_) => println!("Done!"), Err(e) => eprintln!("{:?}", e), } }
  • 13.
    The Fix isSimple, but... use std::thread::spawn; fn main() { let v: &’static str = "Hello SoCraTeN"; // The reference is cloned and then moved! let mut ts = vec![]; for n in 0..5 { let t = spawn(move || { println!("{v} {n}"); }); ts.push(t); } match ts .into_iter() .map(|t| t.join()) .fold(Ok(()), |a, r| { match r { Ok(_) => a, Err(e) => Err(format!("Something failed {:?}", e)), } }) { Ok(_) => println!("Done!"), Err(e) => eprintln!("{:?}", e), } }
  • 14.
    Another Possible Fix usestd::thread::spawn; fn main() { let v: String = "Hello SoCraTeN".to_string(); let mut ts = vec![]; for n in 0..5 { let v = v.to_string() // String is cloned each run now! let t = spawn(move || { println!("{v} {n}"); }); ts.push(t); } match ts .into_iter() .map(|t| t.join()) .fold(Ok(()), |a, r| { match r { Ok(_) => a, Err(e) => Err(format!("Something failed {:?}", e)), } }) { Ok(_) => println!("Done!"), Err(e) => eprintln!("{:?}", e), } }
  • 15.
    You Can’t UseRc so Just Use Arc use std::sync::Arc; pub struct Arc<T> where T: ?Sized, { /* private fields */ } impl<T> Clone for Arc<T> where T: ?Sized, impl<T> Send for Arc<T> where T: Sync + Send + ?Sized,
  • 16.
    Arc an Example usestd::sync::Arc; use std::thread::spawn; fn main() { let v = Arc::new(String::from("Hello SoCraTeN")); let mut ts = vec![]; for n in 0..5 { let v = v.clone(); let t = spawn(move || { println!("{v} {n}"); }); ts.push(t); } match ts .into_iter() .map(|t| t.join()) .fold(Ok(()), |a, r| { match r { Ok(_) => a, Err(e) => Err(format!("Something failed {:?}", e)), } }) { Ok(_) => println!("Done!"), Err(e) => eprintln!("{:?}", e), } }
  • 17.
    Yes, But HowI Change Stuff? use std::marker::Sync; pub unsafe auto trait Sync { } The precise definition is: a type T is Sync if &T is Send. In other words, if there is no possibility of undefined behavior (including data races) when passing &T references between threads. [...cut...] Types that are not Sync are those that have "interior mutability" in a non-thread-safe form, such as cell::Cell and cell::RefCell. https://doc.rust-lang.org/std/marker/trait.Sync.html
  • 18.
    Change State ThroughThreads use std::sync::{Arc, Mutex}; use std::thread::spawn; fn main() { let v = Arc::new( Mutex::new( String::from("Hello SoCraTeN") ) ); let mut ts = vec![]; for n in 0..5 { let v = v.clone(); let t = spawn(move || { let mut string = v.lock().unwrap(); string.insert_str(6, &format!("{n} ")); println!("{string}"); }); ts.push(t); } /** match… */ } This is possible, but not desirable! Most of the time
  • 19.
    The Atomics Atomic typesprovide primitive shared-memory communication between threads, and are the building blocks of other concurrent types. https://doc.rust-lang.org/std/sync/atomic/index.html
  • 20.
    An Example ofAtomics use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use std::{hint, thread}; fn main() { let spinlock = Arc::new(AtomicUsize::new(1)); let spinlock_clone = Arc::clone(&spinlock); let thread = thread::spawn(move|| { spinlock_clone.store(0, Ordering::SeqCst); }); // Wait for the other thread to release the lock while spinlock.load(Ordering::SeqCst) != 0 { hint::spin_loop(); } if let Err(panic) = thread.join() { println!("Thread had an error: {panic:?}"); } } https://doc.rust-lang.org/std/sync/atomic/index.html
  • 21.
    Atomics Ordering pub enumOrdering { Relaxed, Release, Acquire, AcqRel, SeqCst, }
  • 22.
    Other Locks: Barrier usestd::sync::{Arc, Barrier}; use std::thread; fn main() { let mut handles = Vec::with_capacity(10); let barrier = Arc::new(Barrier::new(10)); for _ in 0..10 { let c = Arc::clone(&barrier); // The same messages will be printed together. // You will NOT see any interleaving. handles.push(thread::spawn(move|| { println!("before wait"); c.wait(); println!("after wait"); })); } // Wait for other threads to finish. for handle in handles { handle.join().unwrap(); } } https://doc.rust-lang.org/std/sync/struct.Barrier.html
  • 23.
    Other Locks: Condvar usestd::sync::{Arc, Mutex, Condvar}; use std::thread; fn main() { let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair2 = Arc::clone(&pair); // Inside of our lock, spawn a new thread, // and then wait for it to start. thread::spawn(move|| { let (lock, cvar) = &*pair2; let mut started = lock.lock().unwrap(); *started = true; // We notify the condvar that the value has changed. cvar.notify_one(); }); // Wait for the thread to start up. let (lock, cvar) = &*pair; let mut started = lock.lock().unwrap(); while !*started { started = cvar.wait(started).unwrap(); } } https://doc.rust-lang.org/std/sync/struct.Condvar.html
  • 24.
    Channels MPSC In RustStandard Library we just have: Multiple Producers Single Consumer channels! pub struct Sender<T> { /* private fields */ } impl Clone, Sync pub struct SyncSender<T> { /* private fields */ } impl Sync pub struct Receiver<T> { /* private fields */ } impl Send
  • 25.
    An Example ofChannels use std::thread; use std::sync::mpsc::channel; fn main() { // Create a simple streaming channel let (tx, rx) = channel(); thread::spawn(move || { tx.send(10).unwrap(); }); assert_eq!(rx.recv().unwrap(), 10); }
  • 26.
    References ● Rust book: TheRust Programming Language – (https://doc.rust-lang.org/book/) ● Rustonomincon – (https://doc.rust-lang.org/nomicon/) ● Crossbeam – (https://github.com/crossbeam-rs/crossbeam) ● Jon Gjengset YouTube channel – (https://www.youtube.com/c/JonGjengset) ● Rust Playground – (https://play.rust-lang.org/)
  • 27.
    Thank You! Have anyquestion? Please go ahead! :)