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