Library
Module
Module type
Parameter
Class
Class type
Basic futex-like awaitable atomic location for Picos
.
module Awaitable : sig ... end
An awaitable atomic location.
We first open the library to bring the Awaitable
module into scope:
# open Picos_std_awaitable
Mutex
Here is a basic mutex implementation using awaitables:
module Mutex = struct
type t = int Awaitable.t
let create ?padded () = Awaitable.make ?padded 0
let lock t =
if not (Awaitable.compare_and_set t 0 1) then
while Awaitable.exchange t 2 <> 0 do
Awaitable.await t 2
done
let unlock t =
let before = Awaitable.fetch_and_add t (-1) in
if before = 2 then begin
Awaitable.set t 0;
Awaitable.signal t
end
end
The above mutex outperforms most other mutexes under both no/low and high contention scenarios. In no/low contention scenarios the use of fetch_and_add
provides low overhead. In high contention scenarios the above mutex allows unfairness, which avoids performance degradation due to the lock convoy phenomena.
Condition
Let's also implement a condition variable. For that we'll also make use of low level abstractions and operations from the Picos
core library:
# open Picos
To implement a condition variable, we'll use the Awaiter
API:
module Condition = struct
type t = unit Awaitable.t
let create () = Awaitable.make ()
let wait t mutex =
let trigger = Trigger.create () in
let awaiter = Awaitable.Awaiter.add t trigger in
Mutex.unlock mutex;
let lock_forbidden mutex =
let fiber = Fiber.current () in
let forbid = Fiber.exchange fiber ~forbid:true in
Mutex.lock mutex;
Fiber.set fiber ~forbid
in
match Trigger.await trigger with
| None -> lock_forbidden mutex
| Some exn_bt ->
Awaitable.Awaiter.remove awaiter;
lock_forbidden mutex;
Printexc.raise_with_backtrace (fst exn_bt) (snd exn_bt)
let signal = Awaitable.signal
let broadcast = Awaitable.broadcast
end
Notice that the awaitable location used in the above condition variable implementation is never mutated. We just reuse the signaling mechanism of awaitables.