@@ -54,6 +54,7 @@ is distributed under the [ISC license](LICENSE.md).
5454 - [ Blocking transactions] ( #blocking-transactions )
5555 - [ A transactional lock-free leftist heap] ( #a-transactional-lock-free-leftist-heap )
5656 - [ Programming with transactional data structures] ( #programming-with-transactional-data-structures )
57+ - [ The dining philosophers problem] ( #the-dining-philosophers-problem )
5758 - [ A transactional LRU cache] ( #a-transactional-lru-cache )
5859 - [ Programming with primitive operations] ( #programming-with-primitive-operations )
5960- [ 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:
743744# open Kcas_data
744745```
745746
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+
746835#### A transactional LRU cache
747836
748837A LRU or least-recently-used cache is essentially a bounded association table.
0 commit comments