@@ -54,6 +54,7 @@ is distributed under the [ISC license](LICENSE.md).
54
54
- [ Blocking transactions] ( #blocking-transactions )
55
55
- [ A transactional lock-free leftist heap] ( #a-transactional-lock-free-leftist-heap )
56
56
- [ Programming with transactional data structures] ( #programming-with-transactional-data-structures )
57
+ - [ The dining philosophers problem] ( #the-dining-philosophers-problem )
57
58
- [ A transactional LRU cache] ( #a-transactional-lru-cache )
58
59
- [ Programming with primitive operations] ( #programming-with-primitive-operations )
59
60
- [ Designing lock-free algorithms with k-CAS] ( #designing-lock-free-algorithms-with-k-cas )
@@ -743,6 +744,94 @@ first we need to `#require` the package and we'll also open it for convenience:
743
744
# open Kcas_data
744
745
```
745
746
747
+ #### The dining philosophers problem
748
+
749
+ The
750
+ [ dining philosophers problem] ( https://en.wikipedia.org/wiki/Dining_philosophers_problem )
751
+ is a well known classic synchronization problem. It is easy to solve with
752
+ ** kcas** . If you are unfamiliar with the problem, please take a moment to read
753
+ the description of the problem.
754
+
755
+ A handy concurrent data structure for solving the dining philosophers problem is
756
+ the
757
+ [ ` Mvar ` ] ( https://ocaml-multicore.github.io/kcas/doc/kcas_data/Kcas_data/Mvar/index.html )
758
+ or synchronizing variable. A ` 'a Mvar.t ` is basically like a ` 'a option Loc.t `
759
+ with blocking semantics for both
760
+ [ ` take ` ] ( https://ocaml-multicore.github.io/kcas/doc/kcas_data/Kcas_data/Mvar/index.html#val-take )
761
+ and
762
+ [ ` put ` ] ( https://ocaml-multicore.github.io/kcas/doc/kcas_data/Kcas_data/Mvar/index.html#val-put ) .
763
+ For the dining philosophers problem, we can use ` Mvar ` s to store the forks.
764
+
765
+ The problem statement doesn't actually say when to stop. The gist of the
766
+ problem, of course, is that no philosopher should starve. So, we'll make it so
767
+ that we keep a record of how many times each philosopher has eaten. We'll then
768
+ end the experiment as soon as each philosopher has eaten some minimum number of
769
+ times. Programming a philosopher is now straightforward:
770
+
771
+ ``` ocaml
772
+ # let philosopher ~fork_lhs ~fork_rhs ~eaten ~continue =
773
+ let eat () =
774
+ let take_forks ~xt =
775
+ ( Mvar.Xt.take ~xt fork_lhs,
776
+ Mvar.Xt.take ~xt fork_rhs )
777
+ in
778
+ let (lhs, rhs) = Xt.commit { tx = take_forks } in
779
+
780
+ Loc.incr eaten;
781
+
782
+ let drop_forks () =
783
+ Mvar.put fork_lhs lhs;
784
+ Mvar.put fork_rhs rhs
785
+ in
786
+ drop_forks ()
787
+ in
788
+
789
+ while continue () do
790
+ eat ()
791
+ done
792
+ val philosopher :
793
+ fork_lhs:'a Mvar.t ->
794
+ fork_rhs:'b Mvar.t -> eaten:int Loc.t -> continue:(unit -> bool) -> unit =
795
+ <fun>
796
+ ```
797
+
798
+ The dining philosophers main routine then creates the data structures and spawns
799
+ the philosophers:
800
+
801
+ ``` ocaml
802
+ # let dinining_philosophers ~philosophers ~min_rounds =
803
+ assert (3 <= philosophers && 0 <= min_rounds);
804
+ let eaten = Loc.make_array philosophers 0 in
805
+ let continue () =
806
+ eaten
807
+ |> Array.exists @@ fun eaten ->
808
+ Loc.get eaten < min_rounds
809
+ in
810
+ let forks =
811
+ Array.init philosophers @@ fun i ->
812
+ Mvar.create (Some i)
813
+ in
814
+ Array.iter Domain.join @@ Array.init philosophers @@ fun i ->
815
+ Domain.spawn @@ fun () ->
816
+ let fork_lhs = forks.(i)
817
+ and fork_rhs = forks.((i + 1) mod philosophers)
818
+ and eaten = eaten.(i) in
819
+ philosopher ~fork_lhs ~fork_rhs ~eaten ~continue
820
+ val dinining_philosophers : philosophers:int -> min_rounds:int -> unit =
821
+ <fun>
822
+ ```
823
+
824
+ We can now run our solution and confirm that it terminates after each
825
+ philosopher has eaten at least a given number of times:
826
+
827
+ ``` ocaml
828
+ # dinining_philosophers ~philosophers:5 ~min_rounds:1_000
829
+ - : unit = ()
830
+ ```
831
+
832
+ What makes dining philosophers so easy to solve with transactions is that we can
833
+ simply compose two ` take ` operations to take both forks.
834
+
746
835
#### A transactional LRU cache
747
836
748
837
A LRU or least-recently-used cache is essentially a bounded association table.
0 commit comments