From ff8c634a303d4a4133accf5fbff375046f022c7f Mon Sep 17 00:00:00 2001 From: Mica White Date: Sat, 9 Mar 2024 17:48:04 -0500 Subject: Dining Philosophers example --- README.md | 2 +- examples/dining_philosophers.rs | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 examples/dining_philosophers.rs diff --git a/README.md b/README.md index e791696..b1f6892 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ println!("{}", *data.1); Are the ergonomics here any good? This is completely untreaded territory. Maybe there are some useful helper methods we don't have here yet. Maybe `try_lock` should return a `Result`. Maybe `lock_api` or `spin` implements some useful methods that I kept out for this proof of concept. -There might be some promise in trying to prevent circular wait. There could be a special type that only allows the locking mutexes in a specific order. This would still require a thread key so that nobody tries to unlock multiple lock sequences at the same time. But this could improve performance, since we wouldn't need to worry about making sure the lock operations are atomic. +There might be some promise in trying to prevent circular wait. There could be a special type that only allows the locking mutexes in a specific order. This would still require a thread key so that nobody tries to unlock multiple lock sequences at the same time. But this could improve performance, since we wouldn't need to worry about making sure the lock operations are atomic. The biggest problem is that `LockSequence::lock_next` would need to return the same value each time, which is not very ergonomic. Although this library is able to successfully prevent deadlocks, livelocks may still be an issue. Imagine thread 1 gets resource 1, thread 2 gets resource 2, thread 1 realizes it can't get resource 2, thread 2 realizes it can't get resource 1, thread 1 drops resource 1, thread 2 drops resource 2, and then repeat forever. In practice, this situation probably wouldn't last forever. But it would be nice if this could be prevented somehow. diff --git a/examples/dining_philosophers.rs b/examples/dining_philosophers.rs new file mode 100644 index 0000000..9600c8c --- /dev/null +++ b/examples/dining_philosophers.rs @@ -0,0 +1,68 @@ +use std::{thread, time::Duration}; + +use happylock::{LockCollection, Mutex, ThreadKey}; + +static PHILOSOPHERS: [Philosopher; 5] = [ + Philosopher { + name: "Socrates", + left: 0, + right: 1, + }, + Philosopher { + name: "John Rawls", + left: 1, + right: 2, + }, + Philosopher { + name: "Jeremy Bentham", + left: 2, + right: 3, + }, + Philosopher { + name: "John Stuart Mill", + left: 3, + right: 4, + }, + Philosopher { + name: "Judith Butler", + left: 4, + right: 0, + }, +]; + +static FORKS: [Mutex<()>; 5] = [ + Mutex::new(()), + Mutex::new(()), + Mutex::new(()), + Mutex::new(()), + Mutex::new(()), +]; + +struct Philosopher { + name: &'static str, + left: usize, + right: usize, +} + +impl Philosopher { + fn cycle(&self) { + let key = ThreadKey::lock().unwrap(); + thread::sleep(Duration::from_secs(1)); + let forks = LockCollection::new([&FORKS[self.left], &FORKS[self.right]]).unwrap(); + let forks = forks.lock(key); + println!("{} is eating...", self.name); + thread::sleep(Duration::from_secs(1)); + println!("{} is done eating", self.name); + drop(forks); + } +} + +fn main() { + let handles = PHILOSOPHERS + .iter() + .map(|philosopher| thread::spawn(move || philosopher.cycle())); + + for handle in handles { + _ = handle.join(); + } +} -- cgit v1.2.3