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