skip to content

What is a Windows fiber, how does it differ from a thread, and why do so few applications use fibers?

level: middleimportance: nice to knowfreq 20%

answer

  1. stack plus registers, user mode only
  2. the kernel schedules threads, not these
  3. you must yield explicitly
  4. one blocks, all block on that thread
  5. thread-local storage does not follow it

basics

~20 s

A fiber is a user-mode execution context — its own stack and register state — that the application schedules itself by calling SwitchToFiber. The kernel never sees it and schedules only the host thread, so fibers are cooperative, give no parallelism on their own, and one blocking call stalls every fiber on that thread.

solid answer

~50 s

Threads are kernel objects: the scheduler knows about them, can preempt them and can run them on different processors. Fibers are not. A fiber is a saved stack and register context living inside a thread; a thread calls `ConvertThreadToFiber` to become fiber-capable, `CreateFiber` to make more, and `SwitchToFiber` to hand control from one to another. Switching is a user-mode operation with no kernel transition and no scheduler involvement, which makes it cheap, but it is entirely cooperative — a fiber that never yields is never preempted. All fibers on a thread share that thread, so they give concurrency, not parallelism, and any blocking call blocks the lot. They also break the usual assumption that thread-local storage follows the logical task, which is why fiber local storage (`FlsAlloc`) exists. Most code gets the same benefit more safely from the thread pool with asynchronous I/O.

code

c · 31 lines
c
#include <windows.h>
#include <stdio.h>

static LPVOID mainFiber;
static LPVOID workerFiber;

VOID CALLBACK WorkerProc(LPVOID param)
{
    (void)param;
    for (;;) {
        printf("worker fiber running\n");
        SwitchToFiber(mainFiber);   /* cooperative yield; resumes here later */
    }
}

int main(void)
{
    int i;

    mainFiber = ConvertThreadToFiber(NULL);
    workerFiber = CreateFiber(0, WorkerProc, NULL);

    for (i = 0; i < 3; i++) {
        printf("main fiber running\n");
        SwitchToFiber(workerFiber);  /* no kernel transition, no scheduler */
    }

    DeleteFiber(workerFiber);
    ConvertFiberToThread();
    return 0;
}

go deeper

for a junior

Recall that a fiber is scheduled by the application rather than the kernel, and that switching between fibers only happens when the code explicitly asks with SwitchToFiber.

for a middle

Contrast preemption versus cooperative yielding, explain that all fibers on a thread share that thread so a blocking call stalls them all, and know why fiber local storage exists alongside thread-local storage.

for a senior

Explain why a fiber design is invasive: locks and COM apartments track threads, debuggers and profilers show threads, every blocking call must be converted, and each fiber still owns a real stack. Say what you would use instead and why.

for a principal

Be ready to rule on it: adopting fibers means owning a scheduler forever and giving up the tooling and library assumptions everyone else relies on. Argue for asynchronous I/O or language coroutines unless the system already owns its scheduling and can guarantee no uncontrolled blocking call exists anywhere in the process.

## Definition A fiber is the minimum state needed to resume a unit of execution: a stack, the saved registers, and a small amount of bookkeeping. It is created and destroyed by the application, and it is invisible to the kernel scheduler. What the kernel schedules is the thread the fiber happens to be running on. The API is small and explicitly manual: - `ConvertThreadToFiber` (or `ConvertThreadToFiberEx`) turns the calling thread into a fiber so that it has a context to switch away from. Without this the thread has nowhere to return to. - `CreateFiber` / `CreateFiberEx` allocates a fiber with a start routine and a stack. - `SwitchToFiber` saves the current fiber's context and resumes the target's. It never returns until somebody switches back. - `DeleteFiber` frees one, and `ConvertFiberToThread` reverses the first step. - `GetCurrentFiber` and `GetFiberData` retrieve identity and the parameter passed at creation. There is no scheduler in that list. Deciding which fiber runs next is entirely the application's job. ## The differences that matter **Preemption.** The kernel can stop a thread at any instruction when a higher-priority thread becomes ready or the quantum expires. Nothing stops a fiber. It runs until it calls `SwitchToFiber` or its thread is itself preempted — and when the thread is rescheduled, the same fiber resumes. A fiber with an infinite loop and no yield halts that thread's fibers permanently. **Parallelism.** Threads on a multiprocessor run genuinely simultaneously. Fibers on one thread never do: exactly one of them is executing at any moment. Fibers give you *interleaving under your control*, not more CPU. To use several cores you still need several threads, and then you are scheduling fibers onto threads yourself — which is most of a scheduler. **Blocking.** A thread that blocks in a synchronous call lets the kernel run something else. A fiber that blocks blocks its host thread, and therefore every other fiber on it. In a fiber-based design, every blocking call must be replaced by something that yields to the fiber scheduler instead, which is exactly the discipline that makes fibers invasive. **Switch cost.** A fiber switch is a user-mode save/restore with no ring transition and no scheduler bookkeeping, so it is markedly cheaper than a thread context switch. That saving is the whole reason fibers exist. ## The traps **Thread-local storage.** TLS is attached to the *thread*. Switch fibers on that thread and the new fiber sees the previous fiber's TLS values, because nothing was swapped. Any code that stashes per-task state in TLS — logging correlation ids, error state, cached connections, allocator caches — silently corrupts across fibers, and the corruption is data-dependent and hard to reproduce. Fiber local storage (`FlsAlloc`, `FlsSetValue`, `FlsGetValue`, `FlsFree`) exists to fix this, but only for code you can change. Libraries you link against generally assume thread affinity. ```c DWORD slot = FlsAlloc(NULL); /* per-fiber, not per-thread */ FlsSetValue(slot, requestContext); /* travels with the fiber */ ``` **Everything that assumes a thread.** Locks that record an owning thread cannot tell two fibers apart, so a critical section entered on one fiber and released on another looks legitimate to the runtime and destroys the mutual exclusion you thought you had. COM apartments are thread-based. Structured exception handling and stack unwinding assume one logical stack per thread. Debuggers and profilers show threads and their stacks — a fiber-switching process shows you the stack of whichever fiber is current, so a hang looks like it is somewhere it is not. **Stack sizing.** Each fiber owns a real stack that has to be committed and sized. Thousands of fibers means thousands of stacks, which is where the memory savings people expect from "lightweight threads" quietly disappear. ## Where they are actually used Fibers found their niche where an application already had its own scheduler and could afford to control every blocking call: SQL Server's *lightweight pooling* option is the canonical example, and Microsoft's own guidance is that most workloads should leave it off. Some language runtimes and game engines have used fibers to implement coroutine-style task systems for the same reason. For ordinary application code the alternatives are simply better. Asynchronous I/O plus the Windows thread pool gets you overlapping work without owning a scheduler; language-level coroutines get you the same suspend/resume ergonomics with compiler support, correct local state and tooling that understands them. Fibers remain an interesting exhibit — the clearest illustration in the Win32 API of the difference between what the kernel schedules and what an application schedules — and that is mostly why they come up in interviews.

  • Why does thread-local storage misbehave in a fiber-based design, and what replaces it?
    TLS belongs to the thread, and a fiber switch does not swap it, so a fiber can read state another fiber left behind. Fiber local storage — `FlsAlloc`, `FlsSetValue`, `FlsGetValue`, `FlsFree` — binds values to the fiber instead. The catch is that third-party libraries still use TLS internally, so the fix only covers code you control.
  • If fibers cannot run in parallel, what is the point of them?
    Cheap, deterministic switching. A fiber switch is a user-mode save and restore with no kernel transition and no scheduler involvement, and because it happens only where you asked for it, the application controls interleaving precisely. That suits a system that already owns its own scheduler; it does nothing for a workload that just wants more CPU.
  • What should modern Windows code use instead of fibers?
    Asynchronous I/O with the Windows thread pool for overlapping work without writing a scheduler, or language-level coroutines where the ergonomics of suspend and resume are what you want. Both keep debuggers, profilers and locks working normally, which fiber designs give up.

Threads are employees a manager can interrupt and reassign at will; fibers are tasks on one employee's desk that only change when that employee decides to put one down and pick another up.

saying these in an interview costs you the question

  • Calling a fiber a lightweight thread the kernel schedules
  • Expecting fibers to use multiple cores
  • Assuming a blocked fiber lets other fibers run
  • Keeping per-task state in TLS across fiber switches
  • Believing fibers are preempted at the quantum

context