@@ -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