Fibers and the scheduler
Fiber.new and resume
A fiber is a block with its own stack that runs until it yields and continues where it stopped. It is cooperative: nothing runs until the program calls resume:
ruby -e 'f = Fiber.new { Fiber.yield 1; 2 }; p f.resume, f.resume'
1 2
The first resume runs to the yield and returns 1; the second continues after it and returns 2. A third resume raises FiberError, because the block has finished.
Fiber.yield with a value
Both directions carry a value: resume(x) is the result of the Fiber.yield inside, and the value passed to yield is the result of the resume outside:
ruby -e 'f = Fiber.new { |x| y = Fiber.yield(x + 1); y * 2 }; p f.resume(1); p f.resume(5)'
2 10
That two-way hand-off is what an Enumerator uses internally and what a generator is built from. A fiber is not a thread: only one of them runs at a time, and the switch happens at an explicit yield.
The scheduler
Ruby 3 added a fiber scheduler: a class that the program registers, and the runtime calls it when a fiber blocks on I/O. The shape is three methods:
class Scheduler
def fiber(&block)
Fiber.new(blocking: false, &block)
end
def io_wait(io, events, timeout = nil)
# wait for the socket, then return the events that are ready
end
def kernel_sleep(duration = nil)
# suspend this fiber and resume it after the duration
end
end
Fiber.set_scheduler(Scheduler.new)
With a scheduler installed, sleep and a socket read suspend the fiber instead of the thread, and one thread can hold thousands of waiting connections. The async gem is a scheduler with a production implementation; the standard library ships only the interface.
When a fiber helps
- Many waiting connections. The scheduler case: an HTTP client or a crawler with thousands of sockets and little CPU work.
- A generator. A method that produces values on demand without building the whole array; Enumerator already does it.
- Resumable parsing. A parser that reads a chunk, yields a token and continues, which is easier to write as straight code than as a state machine.
A fiber does not use a second core and does not make a CPU loop faster. For arithmetic, threads and processes are the previous lessons; a fiber is for code that waits.
What to watch for
A fiber that never yields blocks its thread exactly like a method. The
scheduler only helps at the calls it knows: I/O and sleep. A long
computation inside a fiber still stops everything.
Fibers in one thread share the same mutable state with no lock. A fiber
switch can happen in the middle of an update, so the mutex rules from the
thread lesson apply; the scheduler makes the bug less frequent, not
impossible.
Next: Parallel work without a framework
Article author: Arthur Isaev