Each thread has its own stack and local state, and by default no data is shared between threads unless it is immutable data. Generating threads is a very lightweight process; for example, starting tens of thousands of threads only takes a few seconds. The following program does just that, and prints out the numbers 0 to 9,999:
// code from Chapter 9/code/many_threads.rs: use std::thread; static NTHREADS: i32 = 10000; fn main() { println!("************************** Before the start of the threads"); for i in 0..NTHREADS { let _ = thread::spawn(move || { println!("this is thread number {}", i) }); } thread::sleep(time::Duration::from_millis(500)); println!("************************** All threads finished!"); }
Because the numbers are printed in independent threads, the order is not preserved in the output, so it could start with this:
************************** Before the start of the threads this is thread number 1 this is thread number 3 this is thread number 4 this is thread number 2 this is thread number 6 this is thread number 5 this is thread number 0 ... ************************** All threads finished!
Notice we need to add a sleep() method to ensure all spawned threads get finished, otherwise we get error messages such as this:
thread '<unnamed>' panicked at 'cannot access stdout during shutdown'
A question that often arises is how many threads do I have to spawn? The basic rule is, for CPU intensive tasks, have the same number of threads as CPU cores.
This number can be retrieved in Rust by using the num_cpus crate.
Let's make a new project with cargo new many_threads --bin:
- Add the crate dependency to Cargo.toml:
[dependencies] num_cpus = "*"
- Then change main.rs to this:
extern crate num_cpus; fn main() { let ncpus = num_cpus::get(); println!("The number of cpus in this machine is: {}", ncpus); }
- From within the many_threads folder, do a cargo build to install the crate and compile the code. Executing the program with cargo run gives us the following output (dependent on the computer):
The number of cpus in this machine is: 8
For efficiency, it is best to start with this, or any other number of threads in a pool. This functionality is provided by the threadpool crate, which we can get by adding the threadpool = "*" dependency to Cargo.toml and doing a cargo build.
Add the following code to the start of the file:
extern crate threadpool; use std::thread; use std::time;
use threadpool::ThreadPool; Add this code to the main() function: let pool = ThreadPool::new(ncpus); for i in 0..ncpus { pool.execute(move || { println!("this is thread number {}", i) }); } thread::sleep(time::Duration::from_millis(50));
When executed, this yields the following output:
this is thread number 0 this is thread number 5 this is thread number 7 this is thread number 3 this is thread number 4 this is thread number 1 this is thread number 6 this is thread number 2
A thread pool is used for running a number of jobs on a fixed set of parallel worker threads; it creates the given number of worker threads and replenishes the pool if any thread panics.