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