Skip to content

Commit ba41da4

Browse files
committed
WIP: Sleeping barbers
1 parent 27e78c0 commit ba41da4

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
@@ -56,6 +56,7 @@ is distributed under the [ISC license](LICENSE.md).
5656
- [Programming with transactional data structures](#programming-with-transactional-data-structures)
5757
- [The dining philosophers problem](#the-dining-philosophers-problem)
5858
- [A transactional LRU cache](#a-transactional-lru-cache)
59+
- [The sleeping barbers problem](#the-sleeping-barbers-problem)
5960
- [Programming with primitive operations](#programming-with-primitive-operations)
6061
- [Designing lock-free algorithms with k-CAS](#designing-lock-free-algorithms-with-k-cas)
6162
- [Minimize accesses](#minimize-accesses)
@@ -940,6 +941,164 @@ val a_cache : (int, string) cache =
940941
As an exercise, implement an operation to `remove` associations from a cache and
941942
an operation to change the capacity of the cache.
942943

944+
#### The sleeping barbers problem
945+
946+
The
947+
[sleeping barber problem](https://en.wikipedia.org/wiki/Sleeping_barber_problem)
948+
is another classic communication and synchronization problem. Let's write a
949+
solution using **kcas**.
950+
951+
```ocaml
952+
module Barbershop : sig
953+
type ('barber, 'customer) t
954+
val create : int -> ('b, 'c) t
955+
val get_barber_opt : xt:'x Xt.t -> ('b, 'c) t -> 'b option
956+
val try_enqueue : xt:'x Xt.t -> ('b, 'c) t -> 'c -> bool
957+
val get_customer_opt : xt:'x Xt.t -> ('b, 'c) t -> 'c option
958+
val sleep : xt:'x Xt.t -> ('b, 'c) t -> 'b -> unit
959+
val is_closed : xt:'x Xt.t -> ('b, 'c) t -> bool
960+
val close : xt:'x Xt.t -> ('b, 'c) t -> unit
961+
end = struct
962+
type ('barber, 'customer) t = {
963+
sleeping_barbers : 'barber Queue.t;
964+
waiting_customers : 'customer Queue.t;
965+
is_closed : bool Loc.t;
966+
}
967+
968+
let create capacity =
969+
let sleeping_barbers = Queue.create ()
970+
and waiting_customers = Queue.create ~capacity ()
971+
and is_closed = Loc.make false in
972+
{ sleeping_barbers; waiting_customers; is_closed }
973+
974+
let get_barber_opt ~xt bs =
975+
Queue.Xt.take_opt ~xt bs.sleeping_barbers
976+
977+
let try_enqueue ~xt bs customer =
978+
not (Xt.get ~xt bs.is_closed) &&
979+
Queue.Xt.try_add ~xt customer bs.waiting_customers
980+
981+
let get_customer_opt ~xt bs =
982+
Queue.Xt.take_opt ~xt bs.waiting_customers
983+
984+
let sleep ~xt bs barber =
985+
if not (Xt.get ~xt bs.is_closed)
986+
then Queue.Xt.add ~xt barber bs.sleeping_barbers
987+
988+
let is_closed ~xt bs = Xt.get ~xt bs.is_closed
989+
990+
let close ~xt bs =
991+
Xt.set ~xt bs.is_closed true;
992+
Queue.Xt.clear ~xt bs.sleeping_barbers;
993+
Queue.Xt.clear ~xt bs.waiting_customers
994+
end
995+
```
996+
997+
```ocaml
998+
type customer = {
999+
cut_hair : 'x.xt:'x Xt.t -> unit;
1000+
}
1001+
1002+
type barber = {
1003+
wake_up : 'x.xt:'x Xt.t -> customer -> unit;
1004+
}
1005+
```
1006+
1007+
```ocaml
1008+
# let customer shop cuts =
1009+
let clean = Mvar.create None in
1010+
let self = { cut_hair = Mvar.Xt.put clean true } in
1011+
while not (Xt.commit { tx = Barbershop.is_closed shop }) do
1012+
let try_get_barber ~xt =
1013+
match Barbershop.get_barber_opt ~xt shop with
1014+
| None ->
1015+
Barbershop.try_enqueue ~xt shop self
1016+
| Some barber ->
1017+
barber.wake_up ~xt self;
1018+
true
1019+
in
1020+
if Xt.commit { tx = try_get_barber } then
1021+
let try_get_haircut ~xt =
1022+
not (Barbershop.is_closed ~xt shop) &&
1023+
Mvar.Xt.take ~xt clean
1024+
in
1025+
if Xt.commit { tx = try_get_haircut } then
1026+
Loc.incr cuts
1027+
done
1028+
val customer : (barber, customer) Barbershop.t -> int Loc.t -> unit = <fun>
1029+
```
1030+
1031+
```ocaml
1032+
# let barber shop cuts =
1033+
let customer = Mvar.create None in
1034+
let self = { wake_up = Mvar.Xt.put customer } in
1035+
while not (Xt.commit { tx = Barbershop.is_closed shop }) do
1036+
let cut customer =
1037+
Xt.commit { tx = customer.cut_hair };
1038+
Loc.incr cuts
1039+
in
1040+
let try_get_customer ~xt =
1041+
match Barbershop.get_customer_opt ~xt shop with
1042+
| Some _ as some -> some
1043+
| None ->
1044+
Barbershop.sleep ~xt shop self;
1045+
None
1046+
in
1047+
match Xt.commit { tx = try_get_customer } with
1048+
| Some customer -> cut customer
1049+
| None ->
1050+
let sleeping ~xt =
1051+
if Barbershop.is_closed ~xt shop then None
1052+
else Some (Mvar.Xt.take ~xt customer)
1053+
in
1054+
match Xt.commit { tx = sleeping } with
1055+
| Some customer -> cut customer
1056+
| None -> ()
1057+
done
1058+
val barber : (barber, customer) Barbershop.t -> int Loc.t -> unit = <fun>
1059+
```
1060+
1061+
```ocaml
1062+
# let sleeping_barbers ~barbers
1063+
~queue_max
1064+
~customers
1065+
~cuts_per_agent =
1066+
assert (0 < barbers
1067+
&& 0 <= queue_max
1068+
&& 0 <= customers
1069+
&& 0 <= cuts_per_agent);
1070+
let shop = Barbershop.create queue_max in
1071+
let barbers = Array.init barbers @@ fun _ ->
1072+
let cuts = Loc.make 0 in
1073+
(cuts, Domain.spawn (fun () -> barber shop cuts))
1074+
and customers = Array.init customers @@ fun _ ->
1075+
let cuts = Loc.make 0 in
1076+
(cuts, Domain.spawn (fun () -> customer shop cuts))
1077+
in
1078+
let agents = Array.append barbers customers in
1079+
while agents
1080+
|> Array.map fst
1081+
|> Array.exists @@ fun c ->
1082+
Loc.get c < cuts_per_agent do
1083+
Domain.cpu_relax ()
1084+
done;
1085+
Xt.commit { tx = Barbershop.close shop };
1086+
agents
1087+
|> Array.map snd
1088+
|> Array.iter Domain.join
1089+
val sleeping_barbers :
1090+
barbers:int -> queue_max:int -> customers:int -> cuts_per_agent:int -> unit =
1091+
<fun>
1092+
```
1093+
1094+
```ocaml
1095+
# sleeping_barbers ~barbers:2
1096+
~queue_max:1
1097+
~customers:4
1098+
~cuts_per_agent:10
1099+
- : unit = ()
1100+
```
1101+
9431102
### Programming with primitive operations
9441103

9451104
In addition to the transactional interface, **kcas** also provides the

0 commit comments

Comments
 (0)