module

Fiber::ExecutionContext

An execution context creates and manages a dedicated pool of one or more schedulers where fibers will be running in. Each context manages the rules to run, suspend and swap fibers internally.

An execution context groups fibers together. Instead of associating a fiber to a specific system thread, we associate a fiber to an execution context, abstracting which system thread(s) the fibers will run on.

Applications can create any number of execution contexts in parallel. Fibers running in any context can communicate and synchronize with any other fiber running in any context through the usual synchronization primitives such as Channel, WaitGroup or Sync.

When spawning a fiber with ::spawn, it spawns into the execution context of the current fiber, so child fibers execute in the same context as their parent, unless told otherwise (see ExecutionContext#spawn).

Fibers are scoped to the execution context they are spawned into. Once spawned, a fiber cannot move to another execution context, and is always resumed in the same execution context.

Context types

The standard library provides a number of execution context implementations for common use cases.

  • ExecutionContext::Concurrent: Fully concurrent with limited parallelism.

    Fibers run concurrently to each other, never in parallel (only one fiber at a time). They can use simpler and faster synchronization primitives internally (no atomics, limited thread safety), however communication with fibers in other contexts must be safe (e.g. Channel, Sync`, ...). A blocking fiber blocks the entire thread and all other fibers in the context.

  • ExecutionContext::Parallel: Fully concurrent, fully parallel.

    Fibers running in this context can be resumed by multiple system threads in this context. They run concurrently and in parallel to each other (multiple fibers at a time), in addition to running in parallel to any fibers in other contexts. Schedulers steal work from each other. The parallelism can grow and shrink dynamically.

  • ExecutionContext::Isolated: Single fiber in a single system thread without concurrency.

    This is useful for tasks that can block thread execution for a long time (e.g. CPU heavy computation) or must be reactive (e.g. a GUI or game loop). The event-loop works normally and so does communication and synchronization with fibers in other contexts (Channel, WaitGroup, Sync, ...). When the fiber needs to wait, it pauses the thread.

Again, any number of execution contexts can be created (as far as the computer can physically allow). An advantage of starting execution contexts is that it creates execution boundaries, the OS thread scheduler can for example preempt a system thread, allowing fibers in other system threads to run.

The default execution context

The Crystal runtime starts a default execution context exposed as Fiber::ExecutionContext.default. This is where the main fiber is running.

Its parallelism is set to 1 for backwards compatibility reasons; Crystal used to be single-threaded and concurrent only. You can increase the parallelism at any time using Parallel#resize, for example:

count = Fiber::ExecutionContext.default_workers_count
Fiber::ExecutionContext.default.resize(count)

Relationship with system threads

Execution contexts control when and how fibers run, and on which system thread they execute. The term parallelism is the maximum number of fibers that can run in parallel (maximum number of schedulers) but there can be less or more system threads running in practice, for example when a fiber is blocked on a syscall.

There are no guarantees on how a fiber will run on system threads. A fiber can start in thread A, then be resumed and terminated on thread A, B or C. This is true for both the Parallel and Concurrent contexts.

Notable exception: Isolated guarantees that its fiber will always run on the same system thread. During its lifetime, the fiber owns the thread, but only for the fiber's lifetime.

Threads are kept in a thread pool: threads can be started, attached and detached from any context at any time. Threads can be detached from a context and reattached to the same execution context or to another one (Concurrent, Parallel or Isolated).

Constructors

current

Returns the ExecutionContext the current fiber is running in.

Source

Class methods

current?
Source
default

Returns the default ExecutionContext for the process, automatically started when the program started.

The default execution context is currently Parallel but only starts with parallelism set to 1. The parallelism can be changed using Parallel#resize.

Source
default_workers_count

Returns the default maximum parallelism. Can be used to resize the default parallel execution context for example.

Respects the CRYSTAL_WORKERS environment variable if present and valid, and otherwise defaults to the number of logical CPUs available to the process or on the computer.

Source
each

Iterates all execution contexts.

Source
thread_keepalive

How long a parked thread will be kept waiting in the thread pool. Defaults to 5 minutes.

Source
thread_keepalive=(thread_keepalive : Time::Span)

How long a parked thread will be kept waiting in the thread pool. Defaults to 5 minutes.

Source

Instance methods

spawn(*, name : String | Nil = nil, &block : -> ) : Fiber

Creates a new fiber then enqueues it to the execution context.

May be called from any ExecutionContext (i.e. must be thread-safe).

Source

Nested types