22
33For our second project, let’s look at a classic concurrency problem. It’s
44called ‘the dining philosophers’. It was originally conceived by Dijkstra in
5- 1965, but we’ll use the version from [ this paper] [ paper ] by Tony Hoare in 1985.
6-
7- [ paper ] : http://www.usingcsp.com/cspbook.pdf
8-
9- > In ancient times, a wealthy philanthropist endowed a College to accommodate
10- > five eminent philosophers. Each philosopher had a room in which she could
11- > engage in her professional activity of thinking; there was also a common
12- > dining room, furnished with a circular table, surrounded by five chairs, each
13- > labelled by the name of the philosopher who was to sit in it. They sat
14- > anticlockwise around the table. To the left of each philosopher there was
15- > laid a golden fork, and in the centre stood a large bowl of spaghetti, which
16- > was constantly replenished. A philosopher was expected to spend most of her
17- > time thinking; but when she felt hungry, she went to the dining room, sat down
18- > in her own chair, picked up her own fork on her left, and plunged it into the
19- > spaghetti. But such is the tangled nature of spaghetti that a second fork is
20- > required to carry it to the mouth. The philosopher therefore had also to pick
21- > up the fork on her right. When she was finished she would put down both her
22- > forks, get up from her chair, and continue thinking. Of course, a fork can be
23- > used by only one philosopher at a time. If the other philosopher wants it, she
24- > just has to wait until the fork is available again.
5+ 1965, and has been used as an example of the use of concurrency primitives in
6+ many influential works since then.
7+
8+ Plato, Hypatia, Lao, Simone, and Audre are professors of philosophy at a
9+ university. As philosophers, they spend most of their time in their offices
10+ thinking and writing; but occasionally, they need to come out for food. There
11+ is a round table in the common room between their offices, and they each have
12+ a designated seat at it; the university ensures that there is always a fresh
13+ bowl of rice at each seat, so that they may come out and dine whenever they
14+ please. However, due to budgetary constraints, the university has only been
15+ able to afford five chopsticks for the five professors; in order to ensure
16+ that everyone can eat, the chopsticks are arranged between the places at the
17+ table, so each professor can pick up the two adjacent chopstics and begin
18+ eating. Whean each is done eating, they put down their chopsticks in the
19+ original locations, and return to their studies.
2520
2621This classic problem shows off a few different elements of concurrency. The
2722reason is that it's actually slightly tricky to implement: a simple
2823implementation can deadlock. For example, let's consider a simple algorithm
2924that would solve this problem:
3025
31- 1 . A philosopher picks up the fork on their left.
32- 2 . They then pick up the fork on their right.
26+ 1 . A philosopher picks up the chopstick on their left.
27+ 2 . They then pick up the chopstick on their right.
33283 . They eat.
34- 4 . They return the forks .
29+ 4 . They return the chopsticks .
3530
3631Now, let’s imagine this sequence of events:
3732
38- 1 . Philosopher 1 begins the algorithm, picking up the fork on their left.
39- 2 . Philosopher 2 begins the algorithm, picking up the fork on their left.
40- 3 . Philosopher 3 begins the algorithm, picking up the fork on their left.
41- 4 . Philosopher 4 begins the algorithm, picking up the fork on their left.
42- 5 . Philosopher 5 begins the algorithm, picking up the fork on their left.
43- 6 . ... ? All the forks are taken, but nobody can eat!
33+ 1 . Plato begins the algorithm, picking up the chopstick on his left.
34+ 2 . Hypatia begins the algorithm, picking up the chopstick on her left.
35+ 3 . Lao begins the algorithm, picking up the chopstick on his left.
36+ 4 . Simone begins the algorithm, picking up the chopstick on her left.
37+ 5 . Audre begins the algorithm, picking up the chopstick on her left.
38+ 6 . ... ? All the chopsticks are taken, but nobody can eat!
4439
4540There are different ways to solve this problem. We’ll get to our solution in
4641the tutorial itself. For now, let’s get started modelling the problem itself.
@@ -60,11 +55,11 @@ impl Philosopher {
6055}
6156
6257fn main () {
63- let p1 = Philosopher :: new (" Baruch Spinoza " );
64- let p2 = Philosopher :: new (" Gilles Deleuze " );
65- let p3 = Philosopher :: new (" Karl Marx " );
66- let p4 = Philosopher :: new (" Friedrich Nietzsche " );
67- let p5 = Philosopher :: new (" Michel Foucault " );
58+ let p1 = Philosopher :: new (" Plato " );
59+ let p2 = Philosopher :: new (" Hypatia " );
60+ let p3 = Philosopher :: new (" Lao " );
61+ let p4 = Philosopher :: new (" Simone " );
62+ let p5 = Philosopher :: new (" Audre " );
6863}
6964```
7065
@@ -159,11 +154,11 @@ look at `main()` again:
159154# }
160155#
161156fn main () {
162- let p1 = Philosopher :: new (" Baruch Spinoza " );
163- let p2 = Philosopher :: new (" Gilles Deleuze " );
164- let p3 = Philosopher :: new (" Karl Marx " );
165- let p4 = Philosopher :: new (" Friedrich Nietzsche " );
166- let p5 = Philosopher :: new (" Michel Foucault " );
157+ let p1 = Philosopher :: new (" Plato " );
158+ let p2 = Philosopher :: new (" Hypatia " );
159+ let p3 = Philosopher :: new (" Lao " );
160+ let p4 = Philosopher :: new (" Simone " );
161+ let p5 = Philosopher :: new (" Audre " );
167162}
168163```
169164
@@ -176,11 +171,11 @@ that `new()` function, it would look like this:
176171# name : String ,
177172# }
178173fn main () {
179- let p1 = Philosopher { name : " Baruch Spinoza " . to_string () };
180- let p2 = Philosopher { name : " Gilles Deleuze " . to_string () };
181- let p3 = Philosopher { name : " Karl Marx " . to_string () };
182- let p4 = Philosopher { name : " Friedrich Nietzche " . to_string () };
183- let p5 = Philosopher { name : " Michel Foucault " . to_string () };
174+ let p1 = Philosopher { name : " Plato " . to_string () };
175+ let p2 = Philosopher { name : " Hypatia " . to_string () };
176+ let p3 = Philosopher { name : " Lao " . to_string () };
177+ let p4 = Philosopher { name : " Simone " . to_string () };
178+ let p5 = Philosopher { name : " Audre " . to_string () };
184179}
185180```
186181
@@ -211,11 +206,11 @@ impl Philosopher {
211206
212207fn main () {
213208 let philosophers = vec! [
214- Philosopher :: new (" Baruch Spinoza " ),
215- Philosopher :: new (" Gilles Deleuze " ),
216- Philosopher :: new (" Karl Marx " ),
217- Philosopher :: new (" Friedrich Nietzsche " ),
218- Philosopher :: new (" Michel Foucault " ),
209+ Philosopher :: new (" Plato " ),
210+ Philosopher :: new (" Hypatia " ),
211+ Philosopher :: new (" Lao " ),
212+ Philosopher :: new (" Simone " ),
213+ Philosopher :: new (" Audre " ),
219214 ];
220215
221216 for p in & philosophers {
@@ -247,11 +242,11 @@ mention they’re done eating. Running this program should give you the followin
247242output:
248243
249244``` text
250- Baruch Spinoza is done eating.
251- Gilles Deleuze is done eating.
252- Karl Marx is done eating.
253- Friedrich Nietzsche is done eating.
254- Michel Foucault is done eating.
245+ Plato is done eating.
246+ Hypatia is done eating.
247+ Lao is done eating.
248+ Simone is done eating.
249+ Audre is done eating.
255250```
256251
257252Easy enough, they’re all done! We haven’t actually implemented the real problem
@@ -285,11 +280,11 @@ impl Philosopher {
285280
286281fn main () {
287282 let philosophers = vec! [
288- Philosopher :: new (" Baruch Spinoza " ),
289- Philosopher :: new (" Gilles Deleuze " ),
290- Philosopher :: new (" Karl Marx " ),
291- Philosopher :: new (" Friedrich Nietzsche " ),
292- Philosopher :: new (" Michel Foucault " ),
283+ Philosopher :: new (" Plato " ),
284+ Philosopher :: new (" Hypatia " ),
285+ Philosopher :: new (" Lao " ),
286+ Philosopher :: new (" Simone " ),
287+ Philosopher :: new (" Audre " ),
293288 ];
294289
295290 for p in & philosophers {
@@ -323,16 +318,16 @@ simulate the time it takes a philosopher to eat.
323318If you run this program, you should see each philosopher eat in turn:
324319
325320``` text
326- Baruch Spinoza is eating.
327- Baruch Spinoza is done eating.
328- Gilles Deleuze is eating.
329- Gilles Deleuze is done eating.
330- Karl Marx is eating.
331- Karl Marx is done eating.
332- Friedrich Nietzsche is eating.
333- Friedrich Nietzsche is done eating.
334- Michel Foucault is eating.
335- Michel Foucault is done eating.
321+ Plato is eating.
322+ Plato is done eating.
323+ Hypatia is eating.
324+ Hypatia is done eating.
325+ Lao is eating.
326+ Lao is done eating.
327+ Simone is eating.
328+ Simone is done eating.
329+ Audre is eating.
330+ Audre is done eating.
336331```
337332
338333Excellent! We’re getting there. There’s just one problem: we aren’t actually
@@ -366,11 +361,11 @@ impl Philosopher {
366361
367362fn main () {
368363 let philosophers = vec! [
369- Philosopher :: new (" Baruch Spinoza " ),
370- Philosopher :: new (" Gilles Deleuze " ),
371- Philosopher :: new (" Karl Marx " ),
372- Philosopher :: new (" Friedrich Nietzsche " ),
373- Philosopher :: new (" Michel Foucault " ),
364+ Philosopher :: new (" Plato " ),
365+ Philosopher :: new (" Hypatia " ),
366+ Philosopher :: new (" Lao " ),
367+ Philosopher :: new (" Simone " ),
368+ Philosopher :: new (" Audre " ),
374369 ];
375370
376371 let handles : Vec <_ > = philosophers . into_iter (). map (| p | {
@@ -456,33 +451,33 @@ If you run this program, you’ll see that the philosophers eat out of order!
456451We have multi-threading!
457452
458453``` text
459- Gilles Deleuze is eating.
460- Gilles Deleuze is done eating.
461- Friedrich Nietzsche is eating.
462- Friedrich Nietzsche is done eating.
463- Michel Foucault is eating.
464- Baruch Spinoza is eating.
465- Baruch Spinoza is done eating.
466- Karl Marx is eating.
467- Karl Marx is done eating.
468- Michel Foucault is done eating.
454+ Hypatia is eating.
455+ Hypatia is done eating.
456+ Simone is eating.
457+ Simone is done eating.
458+ Audre is eating.
459+ Plato is eating.
460+ Plato is done eating.
461+ Lao is eating.
462+ Lao is done eating.
463+ Audre is done eating.
469464```
470465
471- But what about the forks ? We haven’t modeled them at all yet.
466+ But what about the chopsticks ? We haven’t modeled them at all yet.
472467
473468To do that, let’s make a new ` struct ` :
474469
475470``` rust
476471use std :: sync :: Mutex ;
477472
478473struct Table {
479- forks : Vec <Mutex <()>>,
474+ chopsticks : Vec <Mutex <()>>,
480475}
481476```
482477
483478This ` Table ` has a vector of ` Mutex ` es. A mutex is a way to control
484479concurrency: only one thread can access the contents at once. This is exactly
485- the property we need with our forks . We use an empty tuple, ` () ` , inside the
480+ the property we need with our chopsticks . We use an empty tuple, ` () ` , inside the
486481mutex, since we’re not actually going to use the value, just hold onto it.
487482
488483Let’s modify the program to use the ` Table ` :
@@ -507,8 +502,8 @@ impl Philosopher {
507502 }
508503
509504 fn eat (& self , table : & Table ) {
510- let _left = table . forks [self . left]. lock (). unwrap ();
511- let _right = table . forks [self . right]. lock (). unwrap ();
505+ let _left = table . chopsticks [self . left]. lock (). unwrap ();
506+ let _right = table . chopsticks [self . right]. lock (). unwrap ();
512507
513508 println! (" {} is eating." , self . name);
514509
@@ -519,11 +514,11 @@ impl Philosopher {
519514}
520515
521516struct Table {
522- forks : Vec <Mutex <()>>,
517+ chopsticks : Vec <Mutex <()>>,
523518}
524519
525520fn main () {
526- let table = Arc :: new (Table { forks : vec! [
521+ let table = Arc :: new (Table { chopsticks : vec! [
527522 Mutex :: new (()),
528523 Mutex :: new (()),
529524 Mutex :: new (()),
@@ -532,11 +527,11 @@ fn main() {
532527 ]});
533528
534529 let philosophers = vec! [
535- Philosopher :: new (" Baruch Spinoza " , 0 , 1 ),
536- Philosopher :: new (" Gilles Deleuze " , 1 , 2 ),
537- Philosopher :: new (" Karl Marx " , 2 , 3 ),
538- Philosopher :: new (" Friedrich Nietzsche " , 3 , 4 ),
539- Philosopher :: new (" Michel Foucault " , 0 , 4 ),
530+ Philosopher :: new (" Plato " , 0 , 1 ),
531+ Philosopher :: new (" Hypatia " , 1 , 2 ),
532+ Philosopher :: new (" Lao " , 2 , 3 ),
533+ Philosopher :: new (" Simone " , 3 , 4 ),
534+ Philosopher :: new (" Audre " , 0 , 4 ),
540535 ];
541536
542537 let handles : Vec <_ > = philosophers . into_iter (). map (| p | {
@@ -572,9 +567,9 @@ struct Philosopher {
572567```
573568
574569We need to add two more fields to our ` Philosopher ` . Each philosopher is going
575- to have two forks : the one on their left, and the one on their right.
570+ to have two chopsticks : the one on their left, and the one on their right.
576571We’ll use the ` usize ` type to indicate them, as it’s the type that you index
577- vectors with. These two values will be the indexes into the ` forks ` our ` Table `
572+ vectors with. These two values will be the indexes into the ` chopsticks ` our ` Table `
578573has.
579574
580575``` rust,ignore
@@ -592,8 +587,8 @@ We now need to construct those `left` and `right` values, so we add them to
592587
593588``` rust,ignore
594589fn eat(&self, table: &Table) {
595- let _left = table.forks [self.left].lock().unwrap();
596- let _right = table.forks [self.right].lock().unwrap();
590+ let _left = table.chopsticks [self.left].lock().unwrap();
591+ let _right = table.chopsticks [self.right].lock().unwrap();
597592
598593 println!("{} is eating.", self.name);
599594
@@ -604,8 +599,8 @@ fn eat(&self, table: &Table) {
604599```
605600
606601We have two new lines. We’ve also added an argument, ` table ` . We access the
607- ` Table ` ’s list of forks , and then use ` self.left ` and ` self.right ` to access
608- the fork at that particular index. That gives us access to the ` Mutex ` at that
602+ ` Table ` ’s list of chopsticks , and then use ` self.left ` and ` self.right ` to access
603+ the chopstick at that particular index. That gives us access to the ` Mutex ` at that
609604index, and we call ` lock() ` on it. If the mutex is currently being accessed by
610605someone else, we’ll block until it becomes available.
611606
@@ -626,7 +621,7 @@ What about releasing the lock? Well, that will happen when `_left` and
626621` _right ` go out of scope, automatically.
627622
628623``` rust,ignore
629- let table = Arc::new(Table { forks : vec![
624+ let table = Arc::new(Table { chopsticks : vec![
630625 Mutex::new(()),
631626 Mutex::new(()),
632627 Mutex::new(()),
@@ -643,11 +638,11 @@ count will go up, and when each thread ends, it will go back down.
643638
644639``` rust,ignore
645640let philosophers = vec![
646- Philosopher::new("Baruch Spinoza ", 0, 1),
647- Philosopher::new("Gilles Deleuze ", 1, 2),
648- Philosopher::new("Karl Marx ", 2, 3),
649- Philosopher::new("Friedrich Nietzsche ", 3, 4),
650- Philosopher::new("Michel Foucault ", 0, 4),
641+ Philosopher::new("Plato ", 0, 1),
642+ Philosopher::new("Hypatia ", 1, 2),
643+ Philosopher::new("Lao ", 2, 3),
644+ Philosopher::new("Simone ", 3, 4),
645+ Philosopher::new("Audre ", 0, 4),
651646];
652647```
653648
@@ -678,16 +673,16 @@ With this, our program works! Only two philosophers can eat at any one time,
678673and so you’ll get some output like this:
679674
680675``` text
681- Gilles Deleuze is eating.
682- Friedrich Nietzsche is eating.
683- Friedrich Nietzsche is done eating.
684- Gilles Deleuze is done eating.
685- Baruch Spinoza is eating.
686- Karl Marx is eating.
687- Baruch Spinoza is done eating.
688- Michel Foucault is eating.
689- Karl Marx is done eating.
690- Michel Foucault is done eating.
676+ Hypatia is eating.
677+ Simone is eating.
678+ Simone is done eating.
679+ Hypatia is done eating.
680+ Plato is eating.
681+ Lao is eating.
682+ Plato is done eating.
683+ Audre is eating.
684+ Lao is done eating.
685+ Audre is done eating.
691686```
692687
693688Congrats! You’ve implemented a classic concurrency problem in Rust.
0 commit comments