@@ -56,6 +56,7 @@ is distributed under the [ISC license](LICENSE.md).
56
56
- [ Programming with transactional data structures] ( #programming-with-transactional-data-structures )
57
57
- [ The dining philosophers problem] ( #the-dining-philosophers-problem )
58
58
- [ A transactional LRU cache] ( #a-transactional-lru-cache )
59
+ - [ The sleeping barbers problem] ( #the-sleeping-barbers-problem )
59
60
- [ Programming with primitive operations] ( #programming-with-primitive-operations )
60
61
- [ Designing lock-free algorithms with k-CAS] ( #designing-lock-free-algorithms-with-k-cas )
61
62
- [ Minimize accesses] ( #minimize-accesses )
@@ -940,6 +941,164 @@ val a_cache : (int, string) cache =
940
941
As an exercise, implement an operation to ` remove ` associations from a cache and
941
942
an operation to change the capacity of the cache.
942
943
944
+ #### The sleeping barbers problem
945
+
946
+ The
947
+ [ sleeping barber problem] ( https://en.wikipedia.org/wiki/Sleeping_barber_problem )
948
+ is another classic communication and synchronization problem. Let's write a
949
+ solution using ** kcas** .
950
+
951
+ ``` ocaml
952
+ module Barbershop : sig
953
+ type ('barber, 'customer) t
954
+ val create : int -> ('b, 'c) t
955
+ val get_barber_opt : xt:'x Xt.t -> ('b, 'c) t -> 'b option
956
+ val try_enqueue : xt:'x Xt.t -> ('b, 'c) t -> 'c -> bool
957
+ val get_customer_opt : xt:'x Xt.t -> ('b, 'c) t -> 'c option
958
+ val sleep : xt:'x Xt.t -> ('b, 'c) t -> 'b -> unit
959
+ val is_closed : xt:'x Xt.t -> ('b, 'c) t -> bool
960
+ val close : xt:'x Xt.t -> ('b, 'c) t -> unit
961
+ end = struct
962
+ type ('barber, 'customer) t = {
963
+ sleeping_barbers : 'barber Queue.t;
964
+ waiting_customers : 'customer Queue.t;
965
+ is_closed : bool Loc.t;
966
+ }
967
+
968
+ let create capacity =
969
+ let sleeping_barbers = Queue.create ()
970
+ and waiting_customers = Queue.create ~capacity ()
971
+ and is_closed = Loc.make false in
972
+ { sleeping_barbers; waiting_customers; is_closed }
973
+
974
+ let get_barber_opt ~xt bs =
975
+ Queue.Xt.take_opt ~xt bs.sleeping_barbers
976
+
977
+ let try_enqueue ~xt bs customer =
978
+ not (Xt.get ~xt bs.is_closed) &&
979
+ Queue.Xt.try_add ~xt customer bs.waiting_customers
980
+
981
+ let get_customer_opt ~xt bs =
982
+ Queue.Xt.take_opt ~xt bs.waiting_customers
983
+
984
+ let sleep ~xt bs barber =
985
+ if not (Xt.get ~xt bs.is_closed)
986
+ then Queue.Xt.add ~xt barber bs.sleeping_barbers
987
+
988
+ let is_closed ~xt bs = Xt.get ~xt bs.is_closed
989
+
990
+ let close ~xt bs =
991
+ Xt.set ~xt bs.is_closed true;
992
+ Queue.Xt.clear ~xt bs.sleeping_barbers;
993
+ Queue.Xt.clear ~xt bs.waiting_customers
994
+ end
995
+ ```
996
+
997
+ ``` ocaml
998
+ type customer = {
999
+ cut_hair : 'x.xt:'x Xt.t -> unit;
1000
+ }
1001
+
1002
+ type barber = {
1003
+ wake_up : 'x.xt:'x Xt.t -> customer -> unit;
1004
+ }
1005
+ ```
1006
+
1007
+ ``` ocaml
1008
+ # let customer shop cuts =
1009
+ let clean = Mvar.create None in
1010
+ let self = { cut_hair = Mvar.Xt.put clean true } in
1011
+ while not (Xt.commit { tx = Barbershop.is_closed shop }) do
1012
+ let try_get_barber ~xt =
1013
+ match Barbershop.get_barber_opt ~xt shop with
1014
+ | None ->
1015
+ Barbershop.try_enqueue ~xt shop self
1016
+ | Some barber ->
1017
+ barber.wake_up ~xt self;
1018
+ true
1019
+ in
1020
+ if Xt.commit { tx = try_get_barber } then
1021
+ let try_get_haircut ~xt =
1022
+ not (Barbershop.is_closed ~xt shop) &&
1023
+ Mvar.Xt.take ~xt clean
1024
+ in
1025
+ if Xt.commit { tx = try_get_haircut } then
1026
+ Loc.incr cuts
1027
+ done
1028
+ val customer : (barber, customer) Barbershop.t -> int Loc.t -> unit = <fun>
1029
+ ```
1030
+
1031
+ ``` ocaml
1032
+ # let barber shop cuts =
1033
+ let customer = Mvar.create None in
1034
+ let self = { wake_up = Mvar.Xt.put customer } in
1035
+ while not (Xt.commit { tx = Barbershop.is_closed shop }) do
1036
+ let cut customer =
1037
+ Xt.commit { tx = customer.cut_hair };
1038
+ Loc.incr cuts
1039
+ in
1040
+ let try_get_customer ~xt =
1041
+ match Barbershop.get_customer_opt ~xt shop with
1042
+ | Some _ as some -> some
1043
+ | None ->
1044
+ Barbershop.sleep ~xt shop self;
1045
+ None
1046
+ in
1047
+ match Xt.commit { tx = try_get_customer } with
1048
+ | Some customer -> cut customer
1049
+ | None ->
1050
+ let sleeping ~xt =
1051
+ if Barbershop.is_closed ~xt shop then None
1052
+ else Some (Mvar.Xt.take ~xt customer)
1053
+ in
1054
+ match Xt.commit { tx = sleeping } with
1055
+ | Some customer -> cut customer
1056
+ | None -> ()
1057
+ done
1058
+ val barber : (barber, customer) Barbershop.t -> int Loc.t -> unit = <fun>
1059
+ ```
1060
+
1061
+ ``` ocaml
1062
+ # let sleeping_barbers ~barbers
1063
+ ~queue_max
1064
+ ~customers
1065
+ ~cuts_per_agent =
1066
+ assert (0 < barbers
1067
+ && 0 <= queue_max
1068
+ && 0 <= customers
1069
+ && 0 <= cuts_per_agent);
1070
+ let shop = Barbershop.create queue_max in
1071
+ let barbers = Array.init barbers @@ fun _ ->
1072
+ let cuts = Loc.make 0 in
1073
+ (cuts, Domain.spawn (fun () -> barber shop cuts))
1074
+ and customers = Array.init customers @@ fun _ ->
1075
+ let cuts = Loc.make 0 in
1076
+ (cuts, Domain.spawn (fun () -> customer shop cuts))
1077
+ in
1078
+ let agents = Array.append barbers customers in
1079
+ while agents
1080
+ |> Array.map fst
1081
+ |> Array.exists @@ fun c ->
1082
+ Loc.get c < cuts_per_agent do
1083
+ Domain.cpu_relax ()
1084
+ done;
1085
+ Xt.commit { tx = Barbershop.close shop };
1086
+ agents
1087
+ |> Array.map snd
1088
+ |> Array.iter Domain.join
1089
+ val sleeping_barbers :
1090
+ barbers:int -> queue_max:int -> customers:int -> cuts_per_agent:int -> unit =
1091
+ <fun>
1092
+ ```
1093
+
1094
+ ``` ocaml
1095
+ # sleeping_barbers ~barbers:2
1096
+ ~queue_max:1
1097
+ ~customers:4
1098
+ ~cuts_per_agent:10
1099
+ - : unit = ()
1100
+ ```
1101
+
943
1102
### Programming with primitive operations
944
1103
945
1104
In addition to the transactional interface, ** kcas** also provides the
0 commit comments