diff --git a/README.md b/README.md index 322ddb22..dbcc9867 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,55 @@ The user can create several thread pools. These pools use regular posix threads, but the threads are spread across multiple domains (on OCaml 5), which enables parallelism. +The function `Pool.run pool task` runs `task()` on one of the workers +of `pool`, as soon as one is available. No result is returned. + ```ocaml +# #require "threads";; # let pool = Moonpool.Pool.create ~min:4 ();; val pool : Moonpool.Pool.t = +# begin + Moonpool.Pool.run pool + (fun () -> + Thread.delay 0.1; + print_endline "running from the pool"); + print_endline "running from the caller"; + Thread.delay 0.3; (* wait for task to run before returning *) + end ;; +running from the caller +running from the pool +- : unit = () +``` + +The function `Fut.spawn ~on f` schedules `f ()` on the pool `on`, and immediately +returns a _future_ which will eventually hold the result (or an exception). + +The function `Fut.peek` will return the current value, or `None` if the future is +still not completed. +The functions `Fut.wait_block` and `Fut.wait_block_exn` will +block the current thread and wait for the future to complete. +There are some deadlock risks associated with careless use of these, so +be sure to consult the documentation of the `Fut` module. + +```ocaml +# let fut = Moonpool.Fut.spawn ~on:pool + (fun () -> + Thread.delay 0.5; + 1+1);; +val fut : int Moonpool.Fut.t = + +# Moonpool.Fut.peek fut; +- : int Moonpool.Fut.or_error option = None + +# Moonpool.Fut.wait_block_exn fut;; +- : int = 2 +``` + +Some combinators on futures are also provided, e.g. to wait for all futures in +an array to complete: + +```ocaml # let rec fib x = if x <= 1 then 1 else fib (x-1) + fib (x-2);; val fib : int -> int = @@ -46,6 +91,42 @@ Ok 514229; 832040; 1346269; 2178309; 3524578; 5702887; 9227465|] ``` +### Support for `await` + +On OCaml 5, effect handlers can be used to implement `Fut.await : 'a Fut.t -> 'a`. + +The expression `Fut.await some_fut`, when run from inside some thread pool, +suspends its caller task; the suspended task is then parked, and will +be resumed when the future is completed. +The pool worker that was executing this expression, in the mean time, moves +on to another task. +This means that `await` is free of the deadlock risks associated with +`Fut.wait_block`. + +In the following example, we bypass the need for `Fut.join_array` by simply +using regular array functions along with `Fut.await`. + +```ocaml +# let main_fut = + let open Moonpool.Fut in + spawn ~on:pool @@ fun () -> + (* array of sub-futures *) + let tasks: _ Moonpool.Fut.t array = Array.init 100 (fun i -> + spawn ~on:pool (fun () -> + Thread.delay 0.01; + i+1)) + in + Array.fold_left (fun n fut -> n + await fut) 0 tasks + ;; +val main_fut : int Moonpool.Fut.t = + +# let expected_sum = Array.init 100 (fun i->i+1) |> Array.fold_left (+) 0;; +val expected_sum : int = 5050 + +# assert (expected_sum = Moonpool.Fut.wait_block_exn main_fut);; +- : unit = () +``` + ### More intuition To quote [gasche](https://discuss.ocaml.org/t/ann-moonpool-0-1/12387/15):