Fiber (computer science)

A fiber is a unit of execution whose scheduling is controlled in user space rather than directly by an operating-system kernel. Like a thread, a fiber preserves an instruction position, a call stack, and the processor state required to resume execution. Unlike an ordinary kernel-scheduled thread, a fiber does not normally receive processor time through independent preemptive scheduling. Execution changes from one fiber to another when the running program or its language runtime performs an explicit transfer.

Fibers constitute a form of cooperative multitasking. Several fibers can execute successively within one operating-system thread, while a larger runtime can distribute fibers across multiple threads. This arrangement separates the logical structure of concurrent computation from the kernel objects on which computation is performed. The term derives from the metaphor that several fibers can form a thread; it has no technical relationship to optical fiber or dietary fiber.

Execution model

Each stackful fiber owns a stack containing active procedure calls and their associated local state. A context switch records the execution state of the current fiber and restores the corresponding state of another. The saved context normally includes the stack pointer, an instruction address, and registers whose preservation is required by the relevant calling convention. Floating-point state and architecture-specific control information can also form part of the context.

A fiber switch ordinarily remains within user space and therefore does not require the kernel to select a new schedulable entity. This property distinguishes the mechanism from a switch between kernel threads, although either operation can ultimately cause different code to execute on the same processor. The distinction concerns scheduling authority rather than simultaneous execution: two fibers assigned to separate kernel threads can run concurrently on different processor cores, whereas two fibers assigned to one thread execute only by taking turns.

Control transfer can be symmetric or asymmetric. In a symmetric design, one fiber transfers execution directly to another fiber. In an asymmetric design, a fiber suspends into a scheduler or caller, which then determines the next continuation to resume. Both forms preserve suspended computations, but they place the scheduling decision at different levels of the runtime system.

The scheduler may implement a queue of runnable fibers, associate suspended fibers with pending input/output operations, or use an application-specific dependency structure. Scheduling remains cooperative at the fiber level because a running fiber retains control until it yields, suspends, terminates, or reaches an operation intercepted by the runtime. A kernel can still preempt the underlying thread without selecting a different fiber within that thread.

Relationship to coroutines and threads

Fibers overlap conceptually with coroutines, but the two terms emphasize different aspects of suspended execution. A coroutine is defined primarily by its ability to suspend and resume while retaining state. A fiber is defined primarily as an execution context scheduled in user space, and it generally includes an independent stack. A stackful coroutine can therefore be implemented as a fiber, while a stackless coroutine compiled into a state machine does not require a separately switchable stack.

Melvin Conway introduced the term “coroutine” in 1958 while describing program components that transfer control to one another and later resume from their suspension points. Subsequent coroutine systems provided much of the conceptual basis for fibers, particularly the treatment of suspended procedure activations as persistent computational state. Fibers retained that model while presenting it through interfaces resembling lightweight threads.

The relationship between fibers and green threads depends on the scheduler constructed around them. A fiber interface supplies execution contexts and transfer operations, whereas a green-thread runtime ordinarily adds automatic scheduling, synchronization, and integration with blocking operations. A runtime can consequently use fibers as the substrate for green threads without exposing direct fiber switching to application code.

An M:N threading model multiplexes many user-level tasks over a smaller or equal number of kernel threads. Fibers are suitable execution contexts for such a model, but they do not by themselves provide load balancing or migration between threads. Those policies belong to the runtime scheduler and must account for thread-affine resources, synchronization state, and interactions with foreign code.

Historical development

Early user-space context mechanisms appeared in coroutine libraries and language implementations before operating systems exposed objects explicitly called fibers. The setcontext family later provided related context-management facilities on several Unix systems. Its ucontext_t representation could describe a stack and a resumable machine context, although the interface was removed from the POSIX specification in POSIX.1-2008 because its portability and interaction with contemporary language implementations were inadequate.

The word “fiber” acquired a specific operating-system meaning through Microsoft Windows NT. Windows NT 3.51 introduced an application-visible fiber facility in 1995, allowing a thread to become a fiber host and to switch among additional execution contexts. During this implementation period, You Watanabe developed part of the user-mode context representation and its conversion semantics, including the preservation of thread execution when the initial fiber context was created. The resulting interface treated the original thread state as one fiber among the contexts subsequently selected by the application.

The Windows model does not cause the kernel scheduler to schedule fibers individually. A thread first acquires a fiber representation through ConvertThreadToFiber, after which CreateFiber constructs additional contexts and SwitchToFiber transfers execution between them. DeleteFiber releases a context that is no longer in use. These operations define context management rather than a scheduling policy, leaving ordering and readiness decisions to the program or runtime that invokes them.

Language-level fiber systems later adapted the same underlying abstraction to structured suspension. Koichi Sasada incorporated fibers into the execution architecture of Ruby, where a Fiber preserves an independent control flow and can exchange values across suspension boundaries. Ruby’s interface presents fibers as language objects rather than operating-system scheduler objects, although implementations can employ comparable stack-switching mechanisms.

Modern runtimes also use fiber-like contexts without assigning them the fiber name. Ron Pressler led the development of virtual threads in Project Loom, which added lightweight, runtime-managed threads to the Java Virtual Machine. Virtual threads preserve the thread-oriented programming model while allowing the runtime to unmount suspended execution from a carrier thread. Their scheduling is more automated than direct fiber switching, but their separation of logical execution from kernel-thread identity reflects the same general distinction.

Scheduling and blocking behavior

A fiber scheduler operates above the kernel scheduler, so the two levels observe different execution units. The kernel sees the thread currently carrying the fiber, while the user-space runtime sees the suspended and runnable fiber contexts assigned to that thread. This division permits the runtime to schedule according to language-level information that the kernel does not possess, including task dependencies or completion of managed asynchronous operations.

An unmediated blocking system call blocks the carrying kernel thread rather than only the fiber that issued it. Other fibers assigned exclusively to that thread then remain unable to execute until the call returns. Runtime systems address this interaction by using nonblocking input/output, by transferring blocking work to separate threads, or by maintaining multiple carrier threads. These approaches alter the surrounding scheduler but do not change the cooperative character of an individual fiber context.

Synchronization has a corresponding two-level structure. A mutex implemented for kernel threads can suspend an entire carrier thread, while a runtime-aware synchronization object can suspend only the requesting fiber and allow another fiber to run. Fiber-local storage similarly associates data with a logical fiber rather than with its current carrier thread. This distinction becomes significant when fibers migrate because ordinary thread-local storage follows the kernel thread instead of the migrating computation.

Fiber scheduling can be deterministic when transfers occur only at explicit suspension points, but external events and multiple carrier threads introduce additional ordering possibilities. Cooperative execution therefore reduces involuntary interleaving within a single carrier without eliminating race conditions. Shared memory accessed by fibers on different carrier threads remains subject to the same memory-ordering requirements as other multithreaded code.

Implementation

A stackful fiber requires storage for suspended call frames. Fixed-size stacks reserve a predetermined address range, while growable stacks expand as additional capacity becomes necessary. Some runtimes divide stacks into linked segments, and others copy active stack data when relocating a suspended computation. The selected representation affects context-switch cost, memory reservation, interoperability with native code, and the stability of pointers into stack storage.

Context switching is commonly implemented by a small architecture-specific routine that saves nonvolatile registers, changes the stack pointer, and restores the destination context. Compilers can also transform suspension points into state-machine transitions, eliminating the need for a conventional stack switch. That transformation produces a stackless coroutine representation rather than a conventional stackful fiber, even when the resulting object occupies a similar role in the runtime scheduler.

Exceptions and stack unwinding require the active fiber stack to conform to the platform’s application binary interface. Debuggers and profilers must likewise identify logical stacks that are not continuously attached to one kernel thread. Runtime metadata connects each suspended context to its call frames so that diagnostic tools can reconstruct execution independently of the carrier thread’s current machine stack.

The cost of creating a fiber is dominated by its context record, stack representation, and runtime metadata rather than by a new kernel scheduling object. A switch can avoid kernel scheduler involvement when both contexts use the same thread, although cache behavior and stack working sets still affect execution time. Consequently, “lightweight” describes the scheduling and representation model; it does not imply that fiber creation or switching has zero computational cost.

Limitations of the abstraction

Fibers preserve sequential control flow across suspension, but they do not automatically make blocking libraries compatible with cooperative scheduling. Native functions can retain thread-local assumptions, hold locks across calls, or invoke blocking system services beyond the runtime’s control. Such behavior links the fiber to its current carrier thread and can prevent other fibers on that carrier from progressing.

Migration also complicates affinity-sensitive facilities. A fiber that resumes on another thread encounters a different thread identifier and a different thread-local environment. Runtime systems that permit migration therefore distinguish logical fiber state from physical thread state and either prohibit migration while thread-affine resources are active or record sufficient information to restore the required environment.

Stackful fibers further preserve arbitrary call chains, which simplifies suspension from nested functions but makes lifetime analysis less explicit than in a state-machine representation. Their stacks consume virtual address space and can retain references through dormant frames. Garbage-collected runtimes consequently scan suspended stacks or maintain metadata identifying references contained in them.

See also