@@ -57,6 +57,7 @@ is distributed under the [ISC license](LICENSE.md).
5757 - [ Programming with transactional data structures] ( #programming-with-transactional-data-structures )
5858 - [ The dining philosophers problem] ( #the-dining-philosophers-problem )
5959 - [ A transactional LRU cache] ( #a-transactional-lru-cache )
60+ - [ The sleeping barbers problem] ( #the-sleeping-barbers-problem )
6061 - [ Programming with primitive operations] ( #programming-with-primitive-operations )
6162- [ Designing lock-free algorithms with k-CAS] ( #designing-lock-free-algorithms-with-k-cas )
6263 - [ Understand performance] ( #understand-performance )
@@ -1031,6 +1032,164 @@ val a_cache : (int, string) cache =
10311032As an exercise, implement an operation to ` remove ` associations from a cache and
10321033an operation to change the capacity of the cache.
10331034
1035+ #### The sleeping barbers problem
1036+
1037+ The
1038+ [ sleeping barber problem] ( https://en.wikipedia.org/wiki/Sleeping_barber_problem )
1039+ is another classic communication and synchronization problem. Let's write a
1040+ solution using ** kcas** .
1041+
1042+ ``` ocaml
1043+ module Barbershop : sig
1044+ type ('barber, 'customer) t
1045+ val create : int -> ('b, 'c) t
1046+ val get_barber_opt : xt:'x Xt.t -> ('b, 'c) t -> 'b option
1047+ val try_enqueue : xt:'x Xt.t -> ('b, 'c) t -> 'c -> bool
1048+ val get_customer_opt : xt:'x Xt.t -> ('b, 'c) t -> 'c option
1049+ val sleep : xt:'x Xt.t -> ('b, 'c) t -> 'b -> unit
1050+ val is_closed : xt:'x Xt.t -> ('b, 'c) t -> bool
1051+ val close : xt:'x Xt.t -> ('b, 'c) t -> unit
1052+ end = struct
1053+ type ('barber, 'customer) t = {
1054+ sleeping_barbers : 'barber Queue.t;
1055+ waiting_customers : 'customer Queue.t;
1056+ is_closed : bool Loc.t;
1057+ }
1058+
1059+ let create capacity =
1060+ let sleeping_barbers = Queue.create ()
1061+ and waiting_customers = Queue.create ~capacity ()
1062+ and is_closed = Loc.make false in
1063+ { sleeping_barbers; waiting_customers; is_closed }
1064+
1065+ let get_barber_opt ~xt bs =
1066+ Queue.Xt.take_opt ~xt bs.sleeping_barbers
1067+
1068+ let try_enqueue ~xt bs customer =
1069+ not (Xt.get ~xt bs.is_closed) &&
1070+ Queue.Xt.try_add ~xt customer bs.waiting_customers
1071+
1072+ let get_customer_opt ~xt bs =
1073+ Queue.Xt.take_opt ~xt bs.waiting_customers
1074+
1075+ let sleep ~xt bs barber =
1076+ if not (Xt.get ~xt bs.is_closed)
1077+ then Queue.Xt.add ~xt barber bs.sleeping_barbers
1078+
1079+ let is_closed ~xt bs = Xt.get ~xt bs.is_closed
1080+
1081+ let close ~xt bs =
1082+ Xt.set ~xt bs.is_closed true;
1083+ Queue.Xt.clear ~xt bs.sleeping_barbers;
1084+ Queue.Xt.clear ~xt bs.waiting_customers
1085+ end
1086+ ```
1087+
1088+ ``` ocaml
1089+ type customer = {
1090+ cut_hair : 'x.xt:'x Xt.t -> unit;
1091+ }
1092+
1093+ type barber = {
1094+ wake_up : 'x.xt:'x Xt.t -> customer -> unit;
1095+ }
1096+ ```
1097+
1098+ ``` ocaml
1099+ # let customer shop cuts =
1100+ let clean = Mvar.create None in
1101+ let self = { cut_hair = Mvar.Xt.put clean true } in
1102+ while not (Xt.commit { tx = Barbershop.is_closed shop }) do
1103+ let try_get_barber ~xt =
1104+ match Barbershop.get_barber_opt ~xt shop with
1105+ | None ->
1106+ Barbershop.try_enqueue ~xt shop self
1107+ | Some barber ->
1108+ barber.wake_up ~xt self;
1109+ true
1110+ in
1111+ if Xt.commit { tx = try_get_barber } then
1112+ let try_get_haircut ~xt =
1113+ not (Barbershop.is_closed ~xt shop) &&
1114+ Mvar.Xt.take ~xt clean
1115+ in
1116+ if Xt.commit { tx = try_get_haircut } then
1117+ Loc.incr cuts
1118+ done
1119+ val customer : (barber, customer) Barbershop.t -> int Loc.t -> unit = <fun>
1120+ ```
1121+
1122+ ``` ocaml
1123+ # let barber shop cuts =
1124+ let customer = Mvar.create None in
1125+ let self = { wake_up = Mvar.Xt.put customer } in
1126+ while not (Xt.commit { tx = Barbershop.is_closed shop }) do
1127+ let cut customer =
1128+ Xt.commit { tx = customer.cut_hair };
1129+ Loc.incr cuts
1130+ in
1131+ let try_get_customer ~xt =
1132+ match Barbershop.get_customer_opt ~xt shop with
1133+ | Some _ as some -> some
1134+ | None ->
1135+ Barbershop.sleep ~xt shop self;
1136+ None
1137+ in
1138+ match Xt.commit { tx = try_get_customer } with
1139+ | Some customer -> cut customer
1140+ | None ->
1141+ let sleeping ~xt =
1142+ if Barbershop.is_closed ~xt shop then None
1143+ else Some (Mvar.Xt.take ~xt customer)
1144+ in
1145+ match Xt.commit { tx = sleeping } with
1146+ | Some customer -> cut customer
1147+ | None -> ()
1148+ done
1149+ val barber : (barber, customer) Barbershop.t -> int Loc.t -> unit = <fun>
1150+ ```
1151+
1152+ ``` ocaml
1153+ # let sleeping_barbers ~barbers
1154+ ~queue_max
1155+ ~customers
1156+ ~cuts_per_agent =
1157+ assert (0 < barbers
1158+ && 0 <= queue_max
1159+ && 0 <= customers
1160+ && 0 <= cuts_per_agent);
1161+ let shop = Barbershop.create queue_max in
1162+ let barbers = Array.init barbers @@ fun _ ->
1163+ let cuts = Loc.make 0 in
1164+ (cuts, Domain.spawn (fun () -> barber shop cuts))
1165+ and customers = Array.init customers @@ fun _ ->
1166+ let cuts = Loc.make 0 in
1167+ (cuts, Domain.spawn (fun () -> customer shop cuts))
1168+ in
1169+ let agents = Array.append barbers customers in
1170+ while agents
1171+ |> Array.map fst
1172+ |> Array.exists @@ fun c ->
1173+ Loc.get c < cuts_per_agent do
1174+ Domain.cpu_relax ()
1175+ done;
1176+ Xt.commit { tx = Barbershop.close shop };
1177+ agents
1178+ |> Array.map snd
1179+ |> Array.iter Domain.join
1180+ val sleeping_barbers :
1181+ barbers:int -> queue_max:int -> customers:int -> cuts_per_agent:int -> unit =
1182+ <fun>
1183+ ```
1184+
1185+ ``` ocaml
1186+ # sleeping_barbers ~barbers:2
1187+ ~queue_max:1
1188+ ~customers:4
1189+ ~cuts_per_agent:10
1190+ - : unit = ()
1191+ ```
1192+
10341193### Programming with primitive operations
10351194
10361195In addition to the transactional interface, ** kcas** also provides the
0 commit comments