11.4 Futures

► |  | The Racket Reference

1 | Language Model
2 | Notation for Documentation
3 | Syntactic Forms
4 | Datatypes
5 | Structures
6 | Classes and Objects
7 | Units
8 | Contracts
9 | Pattern Matching
10 | Control Flow
11 | Concurrency and Parallelism
12 | Macros
13 | Input and Output
14 | Reflection and Security
15 | Operating System
16 | Memory Management
17 | Unsafe Operations
18 | Running Racket
| Bibliography
| Index

▼ | 11 | Concurrency and Parallelism

11.1 | Threads
11.2 | Synchronization
11.3 | Thread-Local Storage
11.4 | Futures
11.5 | Places
11.6 | Engines

► | 11.4 | Futures

11.4.1 | Creating and Touching Futures
11.4.2 | Future Semaphores
11.4.3 | Future Performance Logging

On this page:

11.4.1`` Creating and Touching Futures
future
touch
futures-enabled?
current-future
future?
would-be-future
processor-count
for/ async
for*/ async
11.4.2`` Future Semaphores
make-fsemaphore
fsemaphore?
fsemaphore-post
fsemaphore-wait
fsemaphore-try-wait?
fsemaphore-count
11.4.3`` Future Performance Logging

top ← prev up next →

#### 11.4`` Futures

> > > Parallelism with Futures in The Racket Guide introduces futures.

(require racket/future) | package: base

The bindings documented in this section are provided by the racket/future and racket libraries, but not racket/base.

> > > Currently, parallel support for future is enabled by default for Windows, Linux x86/x86_64, and Mac OS 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—which rarely support, for example, the guarantee of sequential consistency that is provided for thread-based concurrency. At the same time, operations that seem obviously safe may have a complex enough implementation internally that they cannot run in parallel. See also Parallelism with Futures in The Racket Guide.

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.

##### 11.4.1`` Creating and Touching Futures

> procedure (future thunk) → future?
> thunk : (-> any)
> procedure (touch f) → any
> f : future?

The future procedure returns a future value that encapsulates thunk. The touch function forces the evaluation of the thunk inside the given future, returning the values produced by thunk. After touch forces the evaluation of a thunk, the resulting values are retained by the future in place of thunk, and additional touches of the future return those values.

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.

Example:

> > (let ([f (future (lambda () (+ 1 2)))])
> (list (+ 3 4) (touch f)))
> '(7 3)

> procedure (futures-enabled?) → boolean?

Returns whether parallel support for futures is enabled in the current Racket configuration.

> procedure (current-future) → (or/c #f future?)

Returns the descriptor of the future whose thunk execution is the current continuation; that is, if a future descriptor f is returned, (touch f) will produce the result of the current continuation. If a future thunk itself uses touch, future-thunk executions can be nested, in which case the descriptor of the most immediately executing future is returned. If the current continuation does not return to the touch of any future, the result is #f.

> procedure (future? v) → boolean?
> v : any/c

Returns #t if v is a future value, #f otherwise.

> procedure (would-be-future thunk) → future?
> thunk : (-> any)

Returns a future that never runs in parallel, but that consistently logs all potentially “unsafe” operations during the execution of the future’s thunk (i.e., operations that interfere with parallel execution).

With a normal future, certain circumstances might prevent the logging of unsafe operations. For example, when executed with debug-level logging,

> (touch (future (lambda ()
> (printf "hello1")
> (printf "hello2")
> (printf "hello3"))))

might log three messages, one for each printf invocation. However, if the touch is performed before the future has a chance to start running in parallel, the future thunk evaluates in the same manner as any ordinary thunk, and no unsafe operations are logged. Replacing future with would-be-future ensures the logging of all three calls to printf.

> procedure (processor-count) → exact-positive-integer?

Returns the number of parallel computation units (e.g., processors or cores) that are available on the current machine.

> syntax (for/async (for-clause ...) body ...+)
> syntax (for*/async (for-clause ...) body ...+)

Like for and for*, but each iteration of the body is executed in a separate future, and the futures may be touched in any order.

##### 11.4.2`` Future Semaphores

> procedure (make-fsemaphore init) → fsemaphore?
> init : exact-nonnegative-integer?

Creates and returns a new future semaphore with the counter initially set to init.

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.

> procedure (fsemaphore? v) → boolean?
> v : any/c

Returns #t if v is an future semaphore value, #f otherwise.

> procedure (fsemaphore-post fsema) → void?
> fsema : fsemaphore?

Increments the future semaphore’s internal counter and returns #<void>.

> procedure (fsemaphore-wait fsema) → void?
> fsema : fsemaphore?

Blocks until the internal counter for fsema is non-zero. When the counter is non-zero, it is decremented and fsemaphore-wait returns #<void>.

> procedure (fsemaphore-try-wait? fsema) → boolean?
> fsema : fsemaphore?

Like fsemaphore-wait, but fsemaphore-try-wait? never blocks execution. If fsema’s internal counter is zero, fsemaphore-try-wait? returns #f immediately without decrementing the counter. If fsema’s counter is positive, it is decremented and #t is returned.

> procedure (fsemaphore-count fsema) → exact-nonnegative-integer?
> fsema : fsemaphore?

Returns fsema’s current internal counter value.

##### 11.4.3`` Future Performance Logging

Racket traces 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.

Though textual log output can be viewed directly (or retrieved in code via trace-futures), it is much easier to use the graphical profiler tool provided by future-visualizer.

Future events are logged with the topic 'future. 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:

> (struct future-event (future-id proc-id action time prim-name user-data)
> #: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).

- 'start-overflow-work: like 'start-work, where the future thunk’s work was previously stopped due to an internal stack overflow.

- '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.

- 'overflow (never in process 0): like 'sync or 'block, but for the case that a process encountered an internal stack overflow while evaluating 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-id 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, 'start-0-work, 'start-overflow-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.

An 'block in process 0 is generated when an unsafe operation is handled. This type of event will contain a symbol in the unsafe-op-name field that is the name of the operation. In all other cases, this field contains #f.

The prim-name field will always be #f unless the event occurred on process 0 and its action is either 'block or 'sync. If these conditions are met, prim-name will contain the name of the Racket primitive which required the future to synchronize with the runtime thread (represented as a symbol).

The user-data field may take on a number of different values depending on both the action and prim-name fields:

- 'touch on process 0: contains the integer ID of the future being touched.

- 'sync and prim-name = |allocate memory|: The size (in bytes) of the requested allocation.

- 'sync and prim-name = jit_on_demand: The runtime thread is performing a JIT compilation on behalf of the future future-id. The field contains the name of the function being JIT compiled (as a symbol).

- 'create: A new future was created. The field contains the integer ID of the newly created future.

top ← prev up next →