TRPL: dining philosophers
This commit is contained in:
parent
0ad202671f
commit
2ba61698cc
2 changed files with 691 additions and 0 deletions
|
|
@ -6,6 +6,7 @@
|
|||
* [Hello, Cargo!](hello-cargo.md)
|
||||
* [Learn Rust](learn-rust.md)
|
||||
* [Guessing Game](guessing-game.md)
|
||||
* [Dining Philosophers](dining-philosophers.md)
|
||||
* [Effective Rust](effective-rust.md)
|
||||
* [The Stack and the Heap](the-stack-and-the-heap.md)
|
||||
* [Testing](testing.md)
|
||||
|
|
|
|||
690
src/doc/trpl/dining-philosophers.md
Normal file
690
src/doc/trpl/dining-philosophers.md
Normal file
|
|
@ -0,0 +1,690 @@
|
|||
% Dining Philosophers
|
||||
|
||||
For our second project, let’s look at a classic concurrency problem. It’s
|
||||
called ‘the dining philosophers’. It was originally conceived by Dijkstra in
|
||||
1965, but we’ll use the version from [this paper][paper] by Tony Hoare in 1985.
|
||||
|
||||
[paper]: http://www.usingcsp.com/cspbook.pdf
|
||||
|
||||
> In ancient times, a wealthy philanthropist endowed a College to accommodate
|
||||
> five eminent philosophers. Each philosopher had a room in which he could
|
||||
> engage in his professional activity of thinking; there was also a common
|
||||
> dining room, furnished with a circular table, surrounded by five chairs, each
|
||||
> labelled by the name of the philosopher who was to sit in it. They sat
|
||||
> anticlockwise around the table. To the left of each philosopher there was
|
||||
> laid a golden fork, and in the centre stood a large bowl of spaghetti, which
|
||||
> was constantly replenished. A philosopher was expected to spend most of his
|
||||
> time thinking; but when he felt hungry, he went to the dining room, sat down
|
||||
> in his own chair, picked up his own fork on his left, and plunged it into the
|
||||
> spaghetti. But such is the tangled nature of spaghetti that a second fork is
|
||||
> required to carry it to the mouth. The philosopher therefore had also to pick
|
||||
> up the fork on his right. When we was finished he would put down both his
|
||||
> forks, get up from his chair, and continue thinking. Of course, a fork can be
|
||||
> used by only one philosopher at a time. If the other philosopher wants it, he
|
||||
> just has to wait until the fork is available again.
|
||||
|
||||
This classic problem shows off a few different elements of concurrency. The
|
||||
reason is that it's actually slightly tricky to implement: a simple
|
||||
implementation can deadlock. For example, let's consider a simple algorithm
|
||||
that would solve this problem:
|
||||
|
||||
1. A philosopher picks up the fork on their left.
|
||||
2. They then pick up the fork on their right.
|
||||
3. They eat.
|
||||
4. They return the forks.
|
||||
|
||||
Now, let’s imagine this sequence of events:
|
||||
|
||||
1. Philosopher 1 begins the algorithm, picking up the fork on their left.
|
||||
2. Philosopher 2 begins the algorithm, picking up the fork on their left.
|
||||
3. Philosopher 3 begins the algorithm, picking up the fork on their left.
|
||||
4. Philosopher 4 begins the algorithm, picking up the fork on their left.
|
||||
5. Philosopher 5 begins the algorithm, picking up the fork on their left.
|
||||
6. ... ? All the forks are taken, but nobody can eat!
|
||||
|
||||
There are different ways to solve this problem. We’ll get to our solution in
|
||||
the tutorial itself. For now, let’s get started modelling the problem itself.
|
||||
We’ll start with the philosophers:
|
||||
|
||||
```rust
|
||||
struct Philosopher {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Philosopher {
|
||||
fn new(name: &str) -> Philosopher {
|
||||
Philosopher {
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let p1 = Philosopher::new("Baruch Spinoza");
|
||||
let p2 = Philosopher::new("Gilles Deleuze");
|
||||
let p3 = Philosopher::new("Karl Marx");
|
||||
let p4 = Philosopher::new("Friedrich Nietzsche");
|
||||
let p5 = Philosopher::new("Michel Foucault");
|
||||
}
|
||||
```
|
||||
|
||||
Here, we make a [`struct`][struct] to represent a philosopher. For now,
|
||||
a name is all we need. We choose the [`String`][string] type for the name,
|
||||
rather than `&str`. Generally speaking, working with a type which owns its
|
||||
data is easier than working with one that uses references.
|
||||
|
||||
Let’s continue:
|
||||
|
||||
```rust
|
||||
# struct Philosopher {
|
||||
# name: String,
|
||||
# }
|
||||
impl Philosopher {
|
||||
fn new(name: &str) -> Philosopher {
|
||||
Philosopher {
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This `impl` block lets us define things on `Philosopher` structs. In this case,
|
||||
we define an ‘associated function’ called `new`. The first line looks like this:
|
||||
|
||||
```rust
|
||||
# struct Philosopher {
|
||||
# name: String,
|
||||
# }
|
||||
# impl Philosopher {
|
||||
fn new(name: &str) -> Philosopher {
|
||||
# Philosopher {
|
||||
# name: name.to_string(),
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
```
|
||||
|
||||
We take one argument, a `name`, of type `&str`. This is a reference to another
|
||||
string. It returns an instance of our `Philosopher` struct.
|
||||
|
||||
```rust
|
||||
# struct Philosopher {
|
||||
# name: String,
|
||||
# }
|
||||
# impl Philosopher {
|
||||
# fn new(name: &str) -> Philosopher {
|
||||
Philosopher {
|
||||
name: name.to_string(),
|
||||
}
|
||||
# }
|
||||
# }
|
||||
```
|
||||
|
||||
This creates a new `Philosopher`, and sets its `name` to our `name` argument.
|
||||
Not just the argument itself, though, as we call `.to_string()` on it. This
|
||||
will create a copy of the string that our `&str` points to, and give us a new
|
||||
`String`, which is the type of the `name` field of `Philosopher`.
|
||||
|
||||
Why not accept a `String` directly? It’s nicer to call. If we took a `String`,
|
||||
but our caller had a `&str`, they’d have to call this method themselves. The
|
||||
downside of this flexibility is that we _always_ make a copy. For this small
|
||||
program, that’s not particularly important, as we know we’ll just be using
|
||||
short strings anyway.
|
||||
|
||||
One last thing you’ll notice: we just define a `Philosopher`, and seemingly
|
||||
don’t do anything with it. Rust is an ‘expression based’ language, which means
|
||||
that almost everything in Rust is an expression which returns a value. This is
|
||||
true of functions as well, the last expression is automatically returned. Since
|
||||
we create a new `Philosopher` as the last expression of this function, we end
|
||||
up returning it.
|
||||
|
||||
This name, `new()`, isn’t anything special to Rust, but it is a convention for
|
||||
functions that create new instances of structs. Before we talk about why, let’s
|
||||
look at `main()` again:
|
||||
|
||||
```rust
|
||||
# struct Philosopher {
|
||||
# name: String,
|
||||
# }
|
||||
#
|
||||
# impl Philosopher {
|
||||
# fn new(name: &str) -> Philosopher {
|
||||
# Philosopher {
|
||||
# name: name.to_string(),
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
fn main() {
|
||||
let p1 = Philosopher::new("Baruch Spinoza");
|
||||
let p2 = Philosopher::new("Gilles Deleuze");
|
||||
let p3 = Philosopher::new("Karl Marx");
|
||||
let p4 = Philosopher::new("Friedrich Nietzsche");
|
||||
let p5 = Philosopher::new("Michel Foucault");
|
||||
}
|
||||
```
|
||||
|
||||
Here, we create five variable bindings with five new philosophers. These are my
|
||||
favorite five, but you can substitute anyone you want. If we _didn’t_ define
|
||||
that `new()` function, it would look like this:
|
||||
|
||||
```rust
|
||||
# struct Philosopher {
|
||||
# name: String,
|
||||
# }
|
||||
fn main() {
|
||||
let p1 = Philosopher { name: "Baruch Spinoza".to_string() };
|
||||
let p2 = Philosopher { name: "Gilles Deleuze".to_string() };
|
||||
let p3 = Philosopher { name: "Karl Marx".to_string() };
|
||||
let p4 = Philosopher { name: "Friedrich Nietzche".to_string() };
|
||||
let p5 = Philosopher { name: "Michel Foucault".to_string() };
|
||||
}
|
||||
```
|
||||
|
||||
That’s much noisier. Using `new` has other advantages too, but even in
|
||||
this simple case, it ends up being nicer to use.
|
||||
|
||||
Now that we’ve got the basics in place, there’s a number of ways that we can
|
||||
tackle the broader problem here. I like to start from the end first: let’s
|
||||
set up a way for each philosopher to finish eating. As a tiny step, let’s make
|
||||
a method, and then loop through all the philosophers, calling it:
|
||||
|
||||
```rust
|
||||
struct Philosopher {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Philosopher {
|
||||
fn new(name: &str) -> Philosopher {
|
||||
Philosopher {
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn eat(&self) {
|
||||
println!("{} is done eating.", self.name);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let philosophers = vec![
|
||||
Philosopher::new("Baruch Spinoza"),
|
||||
Philosopher::new("Gilles Deleuze"),
|
||||
Philosopher::new("Karl Marx"),
|
||||
Philosopher::new("Friedrich Nietzsche"),
|
||||
Philosopher::new("Michel Foucault"),
|
||||
];
|
||||
|
||||
for p in &philosophers {
|
||||
p.eat();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let’s look at `main()` first. Rather than have five individual variable
|
||||
bindings for our philosophers, we make a `Vec<T>` of them instead. `Vec<T>` is
|
||||
also called a ‘vector’, and it’s a growable array type. We then use a
|
||||
[`for`][for] loop to iterate through the vector, getting a reference to each
|
||||
philosopher in turn.
|
||||
|
||||
[for]: for-loops.html
|
||||
|
||||
In the body of the loop, we call `p.eat()`, which is defined above:
|
||||
|
||||
```rust,ignore
|
||||
fn eat(&self) {
|
||||
println!("{} is done eating.", self.name);
|
||||
}
|
||||
```
|
||||
|
||||
In Rust, methods take an explicit `self` parameter. That’s why `eat()` is a
|
||||
method, but `new` is an associated function: `new()` has no `self`. For our
|
||||
first version of `eat()`, we just print out the name of the philosopher, and
|
||||
mention they’re done eating. Running this program should give you the following
|
||||
output:
|
||||
|
||||
```text
|
||||
Baruch Spinoza is done eating.
|
||||
Gilles Deleuze is done eating.
|
||||
Karl Marx is done eating.
|
||||
Friedrich Nietzsche is done eating.
|
||||
Michel Foucault is done eating.
|
||||
```
|
||||
|
||||
Easy enough, they’re all done! We haven’t actually implemented the real problem
|
||||
yet, though, so we’re not done yet!
|
||||
|
||||
Next, we want to make our philosophers not just finish eating, but actually
|
||||
eat. Here’s the next version:
|
||||
|
||||
```rust
|
||||
use std::thread;
|
||||
|
||||
struct Philosopher {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Philosopher {
|
||||
fn new(name: &str) -> Philosopher {
|
||||
Philosopher {
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn eat(&self) {
|
||||
println!("{} is eating.", self.name);
|
||||
|
||||
thread::sleep_ms(1000);
|
||||
|
||||
println!("{} is done eating.", self.name);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let philosophers = vec![
|
||||
Philosopher::new("Baruch Spinoza"),
|
||||
Philosopher::new("Gilles Deleuze"),
|
||||
Philosopher::new("Karl Marx"),
|
||||
Philosopher::new("Friedrich Nietzsche"),
|
||||
Philosopher::new("Michel Foucault"),
|
||||
];
|
||||
|
||||
for p in &philosophers {
|
||||
p.eat();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Just a few changes. Let’s break it down.
|
||||
|
||||
```rust,ignore
|
||||
use std::thread;
|
||||
```
|
||||
|
||||
`use` brings names into scope. We’re going to start using the `thread` module
|
||||
from the standard library, and so we need to `use` it.
|
||||
|
||||
```rust,ignore
|
||||
fn eat(&self) {
|
||||
println!("{} is eating.", self.name);
|
||||
|
||||
thread::sleep_ms(1000);
|
||||
|
||||
println!("{} is done eating.", self.name);
|
||||
}
|
||||
```
|
||||
|
||||
We now print out two messages, with a `sleep_ms()` in the middle. This will
|
||||
simulate the time it takes a philosopher to eat.
|
||||
|
||||
If you run this program, You should see each philosopher eat in turn:
|
||||
|
||||
```text
|
||||
Baruch Spinoza is eating.
|
||||
Baruch Spinoza is done eating.
|
||||
Gilles Deleuze is eating.
|
||||
Gilles Deleuze is done eating.
|
||||
Karl Marx is eating.
|
||||
Karl Marx is done eating.
|
||||
Friedrich Nietzsche is eating.
|
||||
Friedrich Nietzsche is done eating.
|
||||
Michel Foucault is eating.
|
||||
Michel Foucault is done eating.
|
||||
```
|
||||
|
||||
Excellent! We’re getting there. There’s just one problem: we aren’t actually
|
||||
operating in a concurrent fashion, which is a core part of the problem!
|
||||
|
||||
To make our philosophers eat concurrently, we need to make a small change.
|
||||
Here’s the next iteration:
|
||||
|
||||
```rust
|
||||
use std::thread;
|
||||
|
||||
struct Philosopher {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Philosopher {
|
||||
fn new(name: &str) -> Philosopher {
|
||||
Philosopher {
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn eat(&self) {
|
||||
println!("{} is eating.", self.name);
|
||||
|
||||
thread::sleep_ms(1000);
|
||||
|
||||
println!("{} is done eating.", self.name);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let philosophers = vec![
|
||||
Philosopher::new("Baruch Spinoza"),
|
||||
Philosopher::new("Gilles Deleuze"),
|
||||
Philosopher::new("Karl Marx"),
|
||||
Philosopher::new("Friedrich Nietzsche"),
|
||||
Philosopher::new("Michel Foucault"),
|
||||
];
|
||||
|
||||
let handles: Vec<_> = philosophers.into_iter().map(|p| {
|
||||
thread::spawn(move || {
|
||||
p.eat();
|
||||
})
|
||||
}).collect();
|
||||
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All we’ve done is change the loop in `main()`, and added a second one! Here’s the
|
||||
first change:
|
||||
|
||||
```rust,ignore
|
||||
let handles: Vec<_> = philosophers.into_iter().map(|p| {
|
||||
thread::spawn(move || {
|
||||
p.eat();
|
||||
})
|
||||
}).collect();
|
||||
```
|
||||
|
||||
While this is only five lines, they’re a dense four. Let’s break it down.
|
||||
|
||||
```rust,ignore
|
||||
let handles: Vec<_> =
|
||||
```
|
||||
|
||||
We introduce a new binding, called `handles`. We’ve given it this name because
|
||||
we are going to make some new threads, and that will return some handles to those
|
||||
threads that let us control their operation. We need to explicitly annotate
|
||||
the type here, though, due to an issue we’ll talk about later. The `_` is
|
||||
a type placeholder. We’re saying “`handles` is a vector of something, but you
|
||||
can figure out what that something is, Rust.”
|
||||
|
||||
```rust,ignore
|
||||
philosophers.into_iter().map(|p| {
|
||||
```
|
||||
|
||||
We take our list of philosophers and call `into_iter()` on it. This creates an
|
||||
iterator that takes ownership of each philosopher. We need to do this to pass
|
||||
them to our threads. We take that iterator and call `map` on it, which takes a
|
||||
closure as an argument and calls that closure on each element in turn.
|
||||
|
||||
```rust,ignore
|
||||
thread::spawn(move || {
|
||||
p.eat();
|
||||
})
|
||||
```
|
||||
|
||||
Here’s where the concurrency happens. The `thread::spawn` function takes a closure
|
||||
as an argument and executes that closure in a new thread. This closure needs
|
||||
an extra annotation, `move`, to indicate that the closure is going to take
|
||||
ownership of the values it’s capturing. Primarily, the `p` variable of the
|
||||
`map` function.
|
||||
|
||||
Inside the thread, all we do is call `eat()` on `p`.
|
||||
|
||||
```rust,ignore
|
||||
}).collect();
|
||||
```
|
||||
|
||||
Finally, we take the result of all those `map` calls and collect them up.
|
||||
`collect()` will make them into a collection of some kind, which is why we
|
||||
needed to annotate the return type: we want a `Vec<T>`. The elements are the
|
||||
return values of the `thread::spawn` calls, which are handles to those threads.
|
||||
Whew!
|
||||
|
||||
```rust,ignore
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
At the end of `main()`, we loop through the handles and call `join()` on them,
|
||||
which blocks execution until the thread has completed execution. This ensures
|
||||
that the threads complete their work before the program exits.
|
||||
|
||||
If you run this program, you’ll see that the philosophers eat out of order!
|
||||
We have mult-threading!
|
||||
|
||||
```text
|
||||
Gilles Deleuze is eating.
|
||||
Gilles Deleuze is done eating.
|
||||
Friedrich Nietzsche is eating.
|
||||
Friedrich Nietzsche is done eating.
|
||||
Michel Foucault is eating.
|
||||
Baruch Spinoza is eating.
|
||||
Baruch Spinoza is done eating.
|
||||
Karl Marx is eating.
|
||||
Karl Marx is done eating.
|
||||
Michel Foucault is done eating.
|
||||
```
|
||||
|
||||
But what about the forks? We haven’t modeled them at all yet.
|
||||
|
||||
To do that, let’s make a new `struct`:
|
||||
|
||||
```rust
|
||||
use std::sync::Mutex;
|
||||
|
||||
struct Table {
|
||||
forks: Vec<Mutex<()>>,
|
||||
}
|
||||
```
|
||||
|
||||
This `Table` has an vector of `Mutex`es. A mutex is a way to control
|
||||
concurrency: only one thread can access the contents at once. This is exactly
|
||||
the property we need with our forks. We use an empty tuple, `()`, inside the
|
||||
mutex, since we’re not actually going to use the value, just hold onto it.
|
||||
|
||||
Let’s modify the program to use the `Table`:
|
||||
|
||||
```rust
|
||||
use std::thread;
|
||||
use std::sync::{Mutex, Arc};
|
||||
|
||||
struct Philosopher {
|
||||
name: String,
|
||||
left: usize,
|
||||
right: usize,
|
||||
}
|
||||
|
||||
impl Philosopher {
|
||||
fn new(name: &str, left: usize, right: usize) -> Philosopher {
|
||||
Philosopher {
|
||||
name: name.to_string(),
|
||||
left: left,
|
||||
right: right,
|
||||
}
|
||||
}
|
||||
|
||||
fn eat(&self, table: &Table) {
|
||||
let _left = table.forks[self.left].lock().unwrap();
|
||||
let _right = table.forks[self.right].lock().unwrap();
|
||||
|
||||
println!("{} is eating.", self.name);
|
||||
|
||||
thread::sleep_ms(1000);
|
||||
|
||||
println!("{} is done eating.", self.name);
|
||||
}
|
||||
}
|
||||
|
||||
struct Table {
|
||||
forks: Vec<Mutex<()>>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let table = Arc::new(Table { forks: vec![
|
||||
Mutex::new(()),
|
||||
Mutex::new(()),
|
||||
Mutex::new(()),
|
||||
Mutex::new(()),
|
||||
Mutex::new(()),
|
||||
]});
|
||||
|
||||
let philosophers = vec![
|
||||
Philosopher::new("Baruch Spinoza", 0, 1),
|
||||
Philosopher::new("Gilles Deleuze", 1, 2),
|
||||
Philosopher::new("Karl Marx", 2, 3),
|
||||
Philosopher::new("Friedrich Nietzsche", 3, 4),
|
||||
Philosopher::new("Michel Foucault", 0, 4),
|
||||
];
|
||||
|
||||
let handles: Vec<_> = philosophers.into_iter().map(|p| {
|
||||
let table = table.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
p.eat(&table);
|
||||
})
|
||||
}).collect();
|
||||
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Lots of changes! However, with this iteration, we’ve got a working program.
|
||||
Let’s go over the details:
|
||||
|
||||
```rust,ignore
|
||||
use std::sync::{Mutex, Arc};
|
||||
```
|
||||
|
||||
We’re going to use another structure from the `std::sync` package: `Arc<T>`.
|
||||
We’ll talk more about it when we use it.
|
||||
|
||||
```rust,ignore
|
||||
struct Philosopher {
|
||||
name: String,
|
||||
left: usize,
|
||||
right: usize,
|
||||
}
|
||||
```
|
||||
|
||||
We need to add two more fields to our `Philosopher`. Each philosopher is going
|
||||
to have two forks: the one on their left, and the one on their right.
|
||||
We’ll use the `usize` type to indicate them, as it’s the type that you index
|
||||
vectors with. These two values will be the indexes into the `forks` our `Table`
|
||||
has.
|
||||
|
||||
```rust,ignore
|
||||
fn new(name: &str, left: usize, right: usize) -> Philosopher {
|
||||
Philosopher {
|
||||
name: name.to_string(),
|
||||
left: left,
|
||||
right: right,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We now need to construct those `left` and `right` values, so we add them to
|
||||
`new()`.
|
||||
|
||||
```rust,ignore
|
||||
fn eat(&self, table: &Table) {
|
||||
let _left = table.forks[self.left].lock().unwrap();
|
||||
let _right = table.forks[self.right].lock().unwrap();
|
||||
|
||||
println!("{} is eating.", self.name);
|
||||
|
||||
thread::sleep_ms(1000);
|
||||
|
||||
println!("{} is done eating.", self.name);
|
||||
}
|
||||
```
|
||||
|
||||
We have two new lines. We’ve also added an argument, `table`. We access the
|
||||
`Table`’s list of forks, and then use `self.left` and `self.right` to access
|
||||
the fork at that particular index. That gives us access to the `Mutex` at that
|
||||
index, and we call `lock()` on it. If the mutex is currently being accessed by
|
||||
someone else, we’ll block until it becomes available.
|
||||
|
||||
The call to `lock()` might fail, and if it does, we want to crash. In this
|
||||
case, the error that could happen is that the mutex is [‘poisoned’][poison],
|
||||
which is what happens when the thread panics while the lock is held. Since this
|
||||
shouldn’t happen, we just use `unwrap()`.
|
||||
|
||||
[poison]: ../std/sync/struct.Mutex.html#poisoning
|
||||
|
||||
One other odd thing about these lines: we’ve named the results `_left` and
|
||||
`_right`. What’s up with that underscore? Well, we aren’t planning on
|
||||
_using_ the value inside the lock. We just want to acquire it. As such,
|
||||
Rust will warn us that we never use the value. By using the underscore,
|
||||
we tell Rust that this is what we intended, and it won’t throw a warning.
|
||||
|
||||
What about releasing the lock? Well, that will happen when `_left` and
|
||||
`_right` go out of scope, automatically.
|
||||
|
||||
```rust,ignore
|
||||
let table = Arc::new(Table { forks: vec![
|
||||
Mutex::new(()),
|
||||
Mutex::new(()),
|
||||
Mutex::new(()),
|
||||
Mutex::new(()),
|
||||
Mutex::new(()),
|
||||
]});
|
||||
```
|
||||
|
||||
Next, in `main()`, we make a new `Table` and wrap it in an `Arc<T>`.
|
||||
‘arc’ stands for ‘atomic reference count’, and we need that to share
|
||||
our `Table` across multiple threads. As we share it, the reference
|
||||
count will go up, and when each thread ends, it will go back down.
|
||||
|
||||
|
||||
```rust,ignore
|
||||
let philosophers = vec![
|
||||
Philosopher::new("Baruch Spinoza", 0, 1),
|
||||
Philosopher::new("Gilles Deleuze", 1, 2),
|
||||
Philosopher::new("Karl Marx", 2, 3),
|
||||
Philosopher::new("Friedrich Nietzsche", 3, 4),
|
||||
Philosopher::new("Michel Foucault", 0, 4),
|
||||
];
|
||||
```
|
||||
|
||||
We need to pass in our `left` and `right` values to the constructors for our
|
||||
`Philosopher`s. But there’s one more detail here, and it’s _very_ important. If
|
||||
you look at the pattern, it’s all consistent until the very end. Monsieur
|
||||
Foucault should have `4, 0` as arguments, but instead, has `0, 4`. This is what
|
||||
prevents deadlock, actually: one of our philosophers is left handed! This is
|
||||
one way to solve the problem, and in my opinion, it’s the simplest.
|
||||
|
||||
```rust,ignore
|
||||
let handles: Vec<_> = philosophers.into_iter().map(|p| {
|
||||
let table = table.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
p.eat(&table);
|
||||
})
|
||||
}).collect();
|
||||
```
|
||||
|
||||
Finally, inside of our `map()`/`collect()` loop, we call `table.clone()`. The
|
||||
`clone()` method on `Arc<T>` is what bumps up the reference count, and when it
|
||||
goes out of scope, it decrements the count. You’ll notice we can introduce a
|
||||
new binding to `table` here, and it will shadow the old one. This is often used
|
||||
so that you don’t need to come up with two unique names.
|
||||
|
||||
With this, our program works! Only two philosophers can eat at any one time,
|
||||
and so you’ll get some output like this:
|
||||
|
||||
```text
|
||||
Gilles Deleuze is eating.
|
||||
Friedrich Nietzsche is eating.
|
||||
Friedrich Nietzsche is done eating.
|
||||
Gilles Deleuze is done eating.
|
||||
Baruch Spinoza is eating.
|
||||
Karl Marx is eating.
|
||||
Baruch Spinoza is done eating.
|
||||
Michel Foucault is eating.
|
||||
Karl Marx is done eating.
|
||||
Michel Foucault is done eating.
|
||||
```
|
||||
|
||||
Congrats! You’ve implemented a classic concurrency problem in Rust.
|
||||
Loading…
Add table
Add a link
Reference in a new issue