Skip to content

Commit ee1c42d

Browse files
committed
WIP: Sleeping barbers
1 parent b6f9c09 commit ee1c42d

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed

README.md

+159
Original file line numberDiff line numberDiff line change
@@ -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 =
10481049
As an exercise, implement an operation to `remove` associations from a cache and
10491050
an 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

10531212
In addition to the transactional interface, **Kcas** also provides the

0 commit comments

Comments
 (0)