10.4 Futures
Parallelism with Futures in Guide: Racket introduces futures.
Currently, parallel support for future is enabled by default for Windows, Linux x86/x86_64, and Mac OS X x86/x86_64. To enable support for other platforms, use --enable-futures with configure when building Racket.
The future and touch functions from racket/future provide access to parallelism as supported by the hardware and operating system. In contrast to thread, which provides concurrency for arbitrary computations without parallelism, future provides parallelism for limited computations. A future executes its work in parallel (assuming that support for parallelism is available) until it detects an attempt to perform an operation that is too complex for the system to run safely in parallel. Similarly, work in a future is suspended if it depends in some way on the current continuation, such as raising an exception. A suspended computation for a future is resumed when touch is applied to the future.
“Safe” parallel execution of a future means that all operations
provided by the system must be able to enforce contracts and produce
results as documented. “Safe” does not preclude concurrent access to
mutable data that is visible in the program. For example, a computation
in a future might use set! to modify a shared variable, in
which case concurrent assignment to the variable can be visible in other
futures and threads. Furthermore, guarantees about the visibility of
effects and ordering are determined by the operating system and
hardware—
A future never runs in parallel if all of the custodians that allow its creating thread to run are shut down. Such futures can execute through a call to touch, however.
Between a call to future and touch for a given future, the given thunk may run speculatively in parallel to other computations, as described above.
> (let ([f (future (lambda () (+ 1 2)))]) (list (+ 3 4) (touch f))) '(7 3)
(current-future) → (or/c #f future?) |
(make-fsemaphore init) → fsemaphore? |
init : exact-nonnegative-integer? |
A future semaphore is similar to a plain semaphore, but future-semaphore operations can be performed safely in parallel (to synchronize parallel computations). In contrast, operations on plain semaphores are not safe to perform in parallel, and they therefore prevent a computation from continuing in parallel.
(fsemaphore? v) → boolean? |
v : any/c |
(fsemaphore-post fsema) → void? |
fsema : fsemaphore? |
(fsemaphore-wait fsema) → void? |
fsema : fsemaphore? |
(fsemaphore-try-wait? fsema) → boolean? |
fsema : fsemaphore? |
(fsemaphore-count fsema) → exact-nonnegative-integer? |
fsema : fsemaphore? |
10.4.1 Future Performance Logging
Racket futures use logging (see Logging) extensively to report information about how futures are evaluated. Logging output is useful for debugging the performance of programs that use futures.
In addition to its string message, each event logged for a future has a data value that is an instance of a future-event prefab structure:
(define-struct future-event (future-id proc-id action time) #:prefab)
The future-id field is an exact integer that identifies a future, or it is #f when action is 'missing. The future-id field is particularly useful for correlating logged events.
The proc-id fields is an exact, non-negative integer that identifies a parallel process. Process 0 is the main Racket process, where all expressions other than future thunks evaluate.
The time field is an inexact number that represents time in the same way as current-inexact-milliseconds.
The action field is a symbol:
'create: a future was created.
'complete: a future’s thunk evaluated successfully, so that touch will produce a value for the future immediately.
'start-work and 'end-work: a particular process started and ended working on a particular future.
'start-0-work: like 'start-work, but for a future thunk that for some structural reason could not be started in a process other than 0 (e.g., the thunk requires too much local storage to start).
'sync: blocking (processes other than 0) or initiation of handing (process 0) for an “unsafe” operation in a future thunk’s evaluation; the operation must run in process 0.
'block: like 'sync, but for a part of evaluation that must be delayed until the future is touched, because the evaluation may depend on the current continuation.
'touch (never in process 0): like 'sync or 'block, but for a touch operation within a future thunk.
'result or 'abort: waiting or handling for 'sync, 'block, or 'touch ended with a value or an error, respectively.
'suspend (never in process 0): a process blocked by 'sync, 'block, or 'touch abandoned evaluation of a future; some other process may pick up the future later.
'touch-pause and 'touch-resume (in process 0, only): waiting in touch for a future whose thunk is being evaluated in another process.
'missing: one or more events for the process were lost due to internal buffer limits before they could be reported, and the time field reports an upper limit on the time of the missing events; this kind of event is rare.
Assuming no 'missing events, then 'start-work or 'start-0-work is always paired with 'end-work, 'sync, 'block, and 'touch are always paired with 'result, 'abort, or 'suspend, and 'touch-pause is always paired with 'touch-resume.
In process 0, some event pairs can be nested within other event pairs: 'sync, 'block, or 'touch with 'result or 'abort, and 'touch-pause with 'touch-resume.}