Picos_aux_htbl
Lock-free hash table.
The operations provided by this hash table are designed to work as building blocks of non-blocking algorithms. Specifically, the operation signatures and semantics are designed to allow building consensus protocols over arbitrary numbers of processes.
đī¸ Single key reads with this hash table are actually wait-free rather than just lock-free. Internal resizing automatically uses all the threads that are trying to write to the hash table.
First-class module type abbreviation.
val create :
?hashed_type:'k hashed_type ->
?min_buckets:int ->
?max_buckets:int ->
unit ->
('k, 'v) t
create ~hashed_type:(module Key) ()
creates a new empty lock-free hash table.
hashed_type
argument can and usually should be used to specify the equal
and hash
operations on keys. Slow polymorphic equality (=)
and slow polymorphic seeded_hash (Bits64.to_int (Random.bits64 ()))
is used by default.min_buckets
is unspecified and a given min_buckets
may be adjusted by the implementation.max_buckets
is unspecified and a given max_buckets
may be adjusted by the implementation.val hashed_type_of : ('k, 'v) t -> 'k hashed_type
hashed_type_of htbl
returns a copy of the hashed type used when the hash table htbl
was created.
val min_buckets_of : ('k, 'v) t -> int
min_buckets_of htbl
returns the minimum number of buckets of the hash table htbl
.
âšī¸ The returned value may not be the same as given to create
.
val max_buckets_of : ('k, 'v) t -> int
max_buckets_of htbl
returns the maximum number of buckets of the hash table htbl
.
âšī¸ The returned value may not be the same as given to create
.
val find_exn : ('k, 'v) t -> 'k -> 'v
find_exn htbl key
returns the current binding of key
in the hash table htbl
or raises Not_found
if no such binding exists.
val mem : ('k, 'v) t -> 'k -> bool
mem htbl key
determines whether the hash table htbl
has a binding for the key
.
val try_add : ('k, 'v) t -> 'k -> 'v -> bool
try_add htbl key value
tries to add a new binding of key
to value
to the hash table htbl
. Returns true
on success and false
in case the hash table already contained a binding for key
.
val try_set : ('k, 'v) t -> 'k -> 'v -> bool
try_set htbl key value
tries to update an existing binding of key
to value
in the hash table htbl
. Returns true
on success and false
in case the hash table did not contain a binding for key
.
val try_compare_and_set : ('k, 'v) t -> 'k -> 'v -> 'v -> bool
try_compare_and_set htbl key before after
tries to update an existing binding of key
from the before
value to the after
value in the hash table htbl
. Returns true
on success and false
in case the hash table did not contain a binding of key
to the before
value.
âšī¸ The values are compared using physical equality, i.e. the ==
operator.
val set_exn : ('k, 'v) t -> 'k -> 'v -> 'v
set_exn htbl key after
tries to update an existing binding of key
from some before
value to the after
value in the hash table htbl
. Returns the before
value on success or raises Not_found
if no such binding exists.
val try_remove : ('k, 'v) t -> 'k -> bool
try_remove htbl key
tries to remove a binding of key
from the hash table htbl
. Returns true
on success and false
in case the hash table did not contain a binding for key
.
val try_compare_and_remove : ('k, 'v) t -> 'k -> 'v -> bool
try_compare_and_remove htbl key before
tries to remove a binding of key
to the before
value from the hash table htbl
. Returns true
on success and false
in case the hash table did not contain a binding of key
to the before
value.
âšī¸ The values are compared using physical equality, i.e. the ==
operator.
val remove_exn : ('k, 'v) t -> 'k -> 'v
remove_exn htbl key
tries to remove a binding of key
to some before
value from the hash table htbl
. Returns the before
value on success or raises Not_found
if no such binding exists.
val to_seq : ('k, 'v) t -> ('k * 'v) Stdlib.Seq.t
to_seq htbl
takes a snapshot of the bindings in the hash table htbl
and returns them as an association sequence.
đ This is a linear time operation.
val remove_all : ('k, 'v) t -> ('k * 'v) Stdlib.Seq.t
remove_all htbl
takes a snapshot of the bindings in the hash table htbl
, removes the bindings from the hash table, and returns the snapshot as an association sequence.
đ This is a linear time operation.
val find_random_exn : ('k, 'v) t -> 'k
find_random_exn htbl
tries to find a random binding from the hash table htbl
and returns the key of the binding or raises Not_found
in case the hash table is empty.
đ This is an expected constant time operation with worst case linear time complexity.
For the examples we first make a convenience binding:
module Htbl = Picos_aux_htbl
Here is a top-level session using a hash table:
# let t : (int, string) Htbl.t =
Htbl.create
~hashed_type:(module Int) ()
val t : (int, string) Htbl.t = <abstr>
# Htbl.try_add t 42 "The answer"
- : bool = true
# Htbl.try_add t 101 "Basics"
- : bool = true
# Htbl.find_exn t 42
- : string = "The answer"
# Htbl.try_add t 101 "The basics"
- : bool = false
# Htbl.remove_all t |> List.of_seq
- : (int * string) list = [(101, "Basics"); (42, "The answer")]
Below is an example of a randomized lock-free bag implemented using a hash table:
module Bag : sig
type !'v t
val create : unit -> 'v t
val push : 'v t -> 'v -> unit
val pop_exn : 'v t -> 'v
end = struct
type 'v t = (int, 'v) Htbl.t
module Key = struct
type t = int
let equal = Int.equal
let hash = Fun.id
end
let create () = Htbl.create ~hashed_type:(module Key) ()
let rec push t value =
let key = Int64.to_int (Random.bits64 ()) in
if not (Htbl.try_add t key value) then push t value
let rec pop_exn t =
let key = Htbl.find_random_exn t in
try Htbl.remove_exn t key with Not_found -> pop_exn t
end
First of all, as we use random bits as keys, we can use Fun.id
as the hash
function. However, the main idea demonstrated above is that the try_add
and remove_exn
operations can be used as building blocks of lock-free algorithms.
Let's create a simplified version of an external reference counting mechanism.
A Resource
is hashed type whose values need to be disposed:
module type Resource = sig
include Hashtbl.HashedType
val dispose : t -> unit
end
The Reference_counted
functor creates an external reference counting table for resources:
module Reference_counted (Resource : Resource) : sig
type t
val create : Resource.t -> t
val unsafe_get : t -> Resource.t
val incr : t -> unit
val decr : t -> unit
end = struct
type t = Resource.t
let rc_of = Htbl.create ~hashed_type:(module Resource) ()
let create t =
if Htbl.try_add rc_of t 1 then t else invalid_arg "already created"
let unsafe_get = Fun.id
let incr t =
try
let backoff = ref Backoff.default in
while
let n = Htbl.find_exn rc_of t in
not (Htbl.try_compare_and_set rc_of t n (n + 1))
do
backoff := Backoff.once !backoff
done
with Not_found -> invalid_arg "already disposed"
let rec decr t backoff =
match Htbl.find_exn rc_of t with
| n ->
if n < 2 then
if Htbl.try_compare_and_remove rc_of t n then Resource.dispose t
else decr t (Backoff.once backoff)
else if not (Htbl.try_compare_and_set rc_of t n (n - 1)) then
decr t (Backoff.once backoff)
| exception Not_found -> invalid_arg "already disposed"
let decr t = decr t Backoff.default
end
Once again we use lock-free retry loops to update the hash table holding reference counts of resources. Notice that in decr
we can conveniently remove the entire binding from the hash table and avoid leaks. This time we also use a backoff mechanism, because, unlike with the lock-free bag, we don't use randomized keys.