@@ -80,6 +80,7 @@ is distributed under the [ISC license](LICENSE.md).
8080 - [ Programming with transactional data structures] ( #programming-with-transactional-data-structures )
8181 - [ The dining philosophers problem] ( #the-dining-philosophers-problem )
8282 - [ A transactional LRU cache] ( #a-transactional-lru-cache )
83+ - [ The sleeping barbers problem] ( #the-sleeping-barbers-problem )
8384- [ Designing lock-free algorithms with k-CAS] ( #designing-lock-free-algorithms-with-k-cas )
8485 - [ Understand performance] ( #understand-performance )
8586 - [ Minimize accesses] ( #minimize-accesses )
@@ -1048,6 +1049,270 @@ 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+ There are
1060+ [ many ways to solve the problem] ( https://en.wikipedia.org/wiki/Sleeping_barber_problem#Solutions )
1061+ and, in particular, there are concise and subtle implementations using
1062+ semaphores or mutexes. Instead of transliterating a solution using semaphores,
1063+ our approach uses queues and other concurrent data structures. We also solve the
1064+ generalized problem with multiple barbers and we also implement a mechanism to
1065+ close the barbershop. In addition, we abstract the concept of a barbershop,
1066+ where barbers and customers interact. All of this makes our solution longer than
1067+ the well known semaphore based solution. On the other hand, one might argue that
1068+ our solution is a more direct transliteration of the problem. Our solution also
1069+ avoids the starvation problem by using queues.
1070+
1071+ Let's begin by abstracting customer
1072+
1073+ ``` ocaml
1074+ type customer = {
1075+ notify_hair_has_been_cut : 'x.xt:'x Xt.t -> unit;
1076+ }
1077+ ```
1078+
1079+ and barber
1080+
1081+ ``` ocaml
1082+ type barber = {
1083+ wake_up : 'x.xt:'x Xt.t -> customer -> unit;
1084+ }
1085+ ```
1086+
1087+ actors. The idea is that barbers notify customers after finishing their haircut
1088+ and, adhering to the problem description, customers wake up sleeping barbers.
1089+
1090+ A barbershop consists of any number of barbers and waiting customers and can be
1091+ marked as closed:
1092+
1093+ ``` ocaml
1094+ type barbershop = {
1095+ sleeping_barbers : barber Queue.t;
1096+ waiting_customers : customer Queue.t;
1097+ is_closed : bool Loc.t;
1098+ }
1099+ ```
1100+
1101+ The barbershop constructor does not limit the number of barbers, which are
1102+ assumed to bring their own chairs, but does require a specification of the
1103+ number of waiting room chairs for customers:
1104+
1105+ ``` ocaml
1106+ # let barbershop ~num_waiting_chairs =
1107+ let sleeping_barbers = Queue.create ()
1108+ and waiting_customers = Queue.create ~capacity:num_waiting_chairs ()
1109+ and is_closed = Loc.make false in
1110+ { sleeping_barbers; waiting_customers; is_closed }
1111+ val barbershop : num_waiting_chairs:int -> barbershop = <fun>
1112+ ```
1113+
1114+ Although the ` barbershop ` type is not abstract, we treat it as such, so we
1115+ provide a transactional predicate to check whether the barbershop is closed or
1116+ not:
1117+
1118+ ``` ocaml
1119+ # let is_closed ~xt bs = Xt.get ~xt bs.is_closed
1120+ val is_closed : xt:'a Xt.t -> barbershop -> bool = <fun>
1121+ ```
1122+
1123+ To ` close ` a barbershop we set the ` is_closed ` location to ` true ` and clear both
1124+ the sleeping barbers and waiting customers queues:
1125+
1126+ ``` ocaml
1127+ # let close ~xt bs =
1128+ Xt.set ~xt bs.is_closed true;
1129+ Queue.Xt.clear ~xt bs.sleeping_barbers;
1130+ Queue.Xt.clear ~xt bs.waiting_customers
1131+ val close : xt:'a Xt.t -> barbershop -> unit = <fun>
1132+ ```
1133+
1134+ A barber can try to get a customer sitting on a waiting room chair:
1135+
1136+ ``` ocaml
1137+ # let get_sitting_customer_opt ~xt bs =
1138+ Queue.Xt.take_opt ~xt bs.waiting_customers
1139+ val get_sitting_customer_opt : xt:'a Xt.t -> barbershop -> customer option =
1140+ <fun>
1141+ ```
1142+
1143+ Or may go to sleep on the barber's own chair:
1144+
1145+ ``` ocaml
1146+ # let sleep ~xt bs barber =
1147+ if not (is_closed ~xt bs) then
1148+ Queue.Xt.add ~xt barber bs.sleeping_barbers
1149+ val sleep : xt:'a Xt.t -> barbershop -> barber -> unit = <fun>
1150+ ```
1151+
1152+ Note that the ` sleep ` transaction uses the ` is_closed ` predicate. Barbers, as
1153+ well as customers, must leave the shop in case it is closed.
1154+
1155+ A customer can try to find a sleeping barber:
1156+
1157+ ``` ocaml
1158+ # let get_sleeping_barber_opt ~xt bs =
1159+ Queue.Xt.take_opt ~xt bs.sleeping_barbers
1160+ val get_sleeping_barber_opt : xt:'a Xt.t -> barbershop -> barber option =
1161+ <fun>
1162+ ```
1163+
1164+ Or sit on a waiting room chair:
1165+
1166+ ``` ocaml
1167+ # let try_sitting ~xt bs customer =
1168+ not (is_closed ~xt bs) &&
1169+ Queue.Xt.try_add ~xt customer bs.waiting_customers
1170+ val try_sitting : xt:'a Xt.t -> barbershop -> customer -> bool = <fun>
1171+ ```
1172+
1173+ The above ` try_sitting ` transaction is non-blocking. In case the
1174+ ` waiting_customers ` queue is full, it will return ` false ` . With the ` customer `
1175+ actor implementation we'll look at shortly this would mean that customers would
1176+ busy-wait, which works, but potentially wastes energy. Here is a blocking
1177+ version of ` try_sitting ` :
1178+
1179+ ``` ocaml
1180+ # let try_sitting ~xt bs customer =
1181+ not (is_closed ~xt bs) &&
1182+ begin
1183+ Queue.Xt.add ~xt customer bs.waiting_customers;
1184+ true
1185+ end
1186+ val try_sitting : xt:'a Xt.t -> barbershop -> customer -> bool = <fun>
1187+ ```
1188+
1189+ Both of the above ` try_sitting ` transactions work with the ` customer ` actor
1190+ we'll see shortly, but with the latter blocking version we avoid busy-wait.
1191+
1192+ The above constitutes the barbershop abstraction which is a kind of passive
1193+ concurrent data structure. Let's then implement the active participants of the
1194+ problem.
1195+
1196+ A customer tries to get a haircut. When a customer enter the barbershop he first
1197+ tries to find a sleeping barber. If none is available, the customer then tries
1198+ to sit on a waiting room chair. If both fail, then the customer has no option
1199+ except to retry. Otherwise the customer waits to get a haircut. If the shop is
1200+ closed, the customer exits. Here is the ` customer ` actor:
1201+
1202+ ``` ocaml
1203+ # let customer shop cuts =
1204+ let clean = Mvar.create None in
1205+ let self = { notify_hair_has_been_cut = Mvar.Xt.put clean true } in
1206+ while not (Xt.commit { tx = is_closed shop }) do
1207+ let get_barber_opt ~xt =
1208+ match get_sleeping_barber_opt ~xt shop with
1209+ | None ->
1210+ try_sitting ~xt shop self
1211+ | Some barber ->
1212+ barber.wake_up ~xt self;
1213+ true
1214+ in
1215+ if Xt.commit { tx = get_barber_opt } then
1216+ let try_await_haircut ~xt =
1217+ not (is_closed ~xt shop) &&
1218+ Mvar.Xt.take ~xt clean
1219+ in
1220+ if Xt.commit { tx = try_await_haircut } then
1221+ Loc.incr cuts
1222+ done
1223+ val customer : barbershop -> int Loc.t -> unit = <fun>
1224+ ```
1225+
1226+ A barber tries to get a customer to give a haircut. A barber first looks for a
1227+ customer from the waiting room. If none is available, the barber goes to sleep
1228+ waiting for a wakeup from a customer. After obtaining a customer in either way,
1229+ the barber gives a haircut to the customer. Otherwise the shop must be closed
1230+ and the barber exits. Here is the ` barber ` actor:
1231+
1232+ ``` ocaml
1233+ # let barber shop cuts =
1234+ let customer = Mvar.create None in
1235+ let self = { wake_up = Mvar.Xt.put customer } in
1236+ while not (Xt.commit { tx = is_closed shop }) do
1237+ let cut customer =
1238+ Xt.commit { tx = customer.notify_hair_has_been_cut };
1239+ Loc.incr cuts
1240+ in
1241+ let get_customer_opt ~xt =
1242+ match get_sitting_customer_opt ~xt shop with
1243+ | Some _ as some -> some
1244+ | None ->
1245+ sleep ~xt shop self;
1246+ None
1247+ in
1248+ match Xt.commit { tx = get_customer_opt } with
1249+ | Some customer -> cut customer
1250+ | None ->
1251+ let await_wakeup_opt ~xt =
1252+ if is_closed ~xt shop then None
1253+ else Some (Mvar.Xt.take ~xt customer)
1254+ in
1255+ match Xt.commit { tx = await_wakeup_opt } with
1256+ | Some customer -> cut customer
1257+ | None -> ()
1258+ done
1259+ val barber : barbershop -> int Loc.t -> unit = <fun>
1260+ ```
1261+
1262+ To run the problem, a barbershop is created with given number of waiting room
1263+ chairs, is populated by given number of barbers, and a given number of customers
1264+ are spawned. Once each barber has given and each customer has received a given
1265+ number of haircuts the shop is closed. This termination condition seeks to
1266+ demonstrate that no actor is starved. Here is the ` sleeping_barbers ` setup:
1267+
1268+ ``` ocaml
1269+ # let sleeping_barbers ~barbers
1270+ ~num_waiting_chairs
1271+ ~customers
1272+ ~cuts_per_actor =
1273+ assert (0 < barbers
1274+ && 0 <= num_waiting_chairs
1275+ && 0 <= customers
1276+ && 0 <= cuts_per_actor);
1277+ let shop = barbershop ~num_waiting_chairs in
1278+ let barbers = Array.init barbers @@ fun _ ->
1279+ let cuts = Loc.make 0 in
1280+ (cuts, Domain.spawn @@ (fun () -> barber shop cuts))
1281+ and customers = Array.init customers @@ fun _ ->
1282+ let cuts = Loc.make 0 in
1283+ (cuts, Domain.spawn @@ (fun () -> customer shop cuts))
1284+ in
1285+ let agents = Array.append barbers customers in
1286+ while agents
1287+ |> Array.map fst
1288+ |> Array.exists @@ fun c ->
1289+ Loc.get c < cuts_per_actor do
1290+ Domain.cpu_relax ()
1291+ done;
1292+ Xt.commit { tx = close shop };
1293+ agents
1294+ |> Array.map snd
1295+ |> Array.iter Domain.join
1296+ val sleeping_barbers :
1297+ barbers:int ->
1298+ num_waiting_chairs:int -> customers:int -> cuts_per_actor:int -> unit =
1299+ <fun>
1300+ ```
1301+
1302+ Finally, let's try our solution:
1303+
1304+ ``` ocaml
1305+ # sleeping_barbers ~barbers:2
1306+ ~num_waiting_chairs:1
1307+ ~customers:4
1308+ ~cuts_per_actor:10
1309+ - : unit = ()
1310+ ```
1311+
1312+ Like mentioned in the beginning, this is not the most concise solution of the
1313+ sleeping barbers problem, but hopefully this solution can be understood
1314+ relatively easily with respect to the problem description.
1315+
10511316## Designing lock-free algorithms with k-CAS
10521317
10531318The key benefit of k-CAS, or k-CAS-n-CMP, and transactions in particular, is
0 commit comments