use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Clone)]
struct Philosopher {
pub fork1: Arc<Mutex<usize>>,
pub fork2: Arc<Mutex<usize>>,
pub is_eating: bool,
pub id: usize,
}
impl Philosopher {
pub fn eat(&mut self) {
{
if let Ok(_) = self.fork1.lock() && let Ok(_) = self.fork2.lock() {
self.is_eating = true;
println!("Philosopher {} is eating!", self.id);
thread::sleep(Duration::from_secs(1));
}
}
self.is_eating = false;
}
}
fn algo() {
let forks: [Arc<Mutex<usize>>; 5] = core::array::from_fn(|x| Arc::new(Mutex::new(x)));
let philosophers: [Arc<Mutex<Philosopher>>; 5] = core::array::from_fn(|x| Arc::new(Mutex::new(Philosopher {fork1: Arc::clone(&forks[x % 5]), fork2: Arc::clone(&forks[(x + 1) % 5]), is_eating: false, id: x})));
let mut join_handles = Vec::new();
for i in 0..5 {
let value = philosophers.clone();
let join_handle = thread::spawn(move || {
let binding = Arc::clone(&value[i]);
let mut philosopher = binding.lock().unwrap();
loop {
philosopher.eat();
}
});
join_handles.push(join_handle);
}
for join_handle in join_handles {
join_handle.join().unwrap();
}
}
fn main() {
algo();
}
The Dining Philosophers problem seems to have a string of locks followed by another.
Philosopher 1 is eating!
Philosopher 3 is eating!
Philosopher 1 is eating!
Philosopher 3 is eating!
Philosopher 3 is eating!
Philosopher 3 is eating!
Philosopher 3 is eating!
Philosopher 3 is eating!
Philosopher 3 is eating!
Philosopher 2 is eating!
Philosopher 2 is eating!
Philosopher 2 is eating!
Philosopher 2 is eating!
Philosopher 2 is eating!
Philosopher 2 is eating!
Philosopher 1 is eating!
Philosopher 1 is eating!
Philosopher 1 is eating!
I don't get why this happens.
During sleep and a call to lock on a mutex that is blocked, that thread is inactive.
Once sleep finishes that thread is active. The only significant action it performs is releasing the two locks and then reacquiring them on next loop.
The second inactive thread is woken when it has waited on the released mutex but this often takes more time than the first thread, so when active it discovers that the lock has already been taken again and goes inactive again.
By the way, I am very confused about what kind of problem Clippy has with this.
Checking playground v0.0.1 (/playground)
error: this loop never actually loops
--> src/main.rs:41:5
|
41 | / for join_handle in join_handles {
42 | | join_handle.join().unwrap();
43 | | }
| |_____^
|
help: this code is unreachable. Consider moving the reachable parts out
--> src/main.rs:42:9
|
42 | join_handle.join().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#never_loop
= note: `#[deny(clippy::never_loop)]` on by default
help: if you need the first element of the iterator, try writing
|
41 - for join_handle in join_handles {
41 + if let Some(join_handle) = join_handles.into_iter().next() {
|
error: could not compile `playground` (bin "playground") due to 1 previous error
Are you sure you are writing dining-philosopher?
Dining-philosophers is usually deadlocked, but in your case, philosophers are always eating
This is my version of dining-philosophers
use rand::Rng;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
use std::thread::sleep;
use std::time;
use std::time::Duration;
const N: usize = 5;
fn main() {
let v = {
let mut v = vec![];
for _ in 0..N {
v.push(Arc::new(Mutex::new(())));
}
v
};
// for x in v.into_iter(){
// println!("{:p}", x);
// }
let mut threads = Vec::with_capacity(N);
for i in 0..N {
let l = Arc::clone(&v[i]);
let r = Arc::clone(&v[(i + 1) % N]);
threads.push(thread::spawn(move || {
loop {
// thinking random seconds before eating
let secs: u64 = rand::rng().random_range(1..=10);
println!("{i} thinking for {} seconds...", secs);
sleep(Duration::from_secs(secs));
println!("{i} thinking Done");
let x = l.lock().unwrap();
println!("{} get left", i);
let secs: u64 = rand::rng().random_range(1..=10);
println!("{i} will eat {secs} seconds...");
thread::sleep(time::Duration::from_secs(secs));
let y = r.lock().unwrap();
println!("{} start eat", i);
println!("{} finish eat", i);
// hold the lock
// 避免提前释放
// 虽然不加也可以
let _ = *x;
let _ = *y;
}
}));
}
// waiting for all thread terminate
threads.into_iter().for_each(|thread| {
thread
.join()
.expect("The thread creating or execution failed !")
});
}
To avoid deadlock, it should be enough one philosopher doesn't pick forks like all the others. E.g. first left then right, if all the others do the other way around.