Skip to content

Ruby Fibers — Lightweight Concurrency with Fiber Class and Transfers

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Ruby Fibers. We cover key concepts, practical examples, and best practices to help you master this topic.

Ruby Fibers provide cooperative concurrency with explicit control flow using Fiber class, resume and yield methods, and transfers between fibers.

What You'll Learn

  • Creating fibers with Fiber.new
  • Fiber lifecycle: resume, yield, and status
  • Fiber transfer for asymmetric control
  • Enumerator fibers

Why It Matters

Fibers enable lightweight concurrency without OS thread overhead. Frameworks like Falcon and Async use fibers for efficient I/O. DodaZIP uses fibers for streaming file processing.

Real-World Use

Web frameworks handling thousands of concurrent connections, streaming APIs, generator functions, cooperative task scheduling.

flowchart LR
    A["Fibers"] --> B["Fiber.new"]
    B --> C["Resume/Yield"]
    C --> D["Transfer"]
    D --> E["Enumerator Fibers"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Basic Fiber

fiber = Fiber.new do
  puts "Fiber: step 1"
  Fiber.yield
  puts "Fiber: step 2"
  "done"
end

puts "Main: resume 1"
result = fiber.resume
puts "Main: back, got: #{result}"
puts "Main: resume 2"
result = fiber.resume
puts "Main: done, got: #{result}"
# Main: resume 1
# Fiber: step 1
# Main: back, got:
# Main: resume 2
# Fiber: step 2
# Main: done, got: done

Fiber with Parameters

fiber = Fiber.new do |initial|
  puts "Initial: #{initial}"
  received = Fiber.yield("from fiber")
  puts "Received: #{received}"
  "final"
end

puts fiber.resume("hello")
puts fiber.resume("world")
# Initial: hello
# from fiber
# Received: world
# final

Fiber Lifecycle

fiber = Fiber.new do
  Fiber.yield
  "finished"
end

puts "Created: #{fiber.alive?}"
fiber.resume
puts "Yielded: #{fiber.alive?}"
fiber.resume
puts "Done: #{fiber.alive?}"
# Created: true
# Yielded: true
# Done: false

Fiber Transfer

main = Fiber.current

fiber1 = Fiber.new do
  puts "Fiber1: start"
  fiber2.transfer
  puts "Fiber1: end"
end

fiber2 = Fiber.new do
  puts "Fiber2: start"
  fiber1.transfer
  puts "Fiber2: end"
  main.transfer
end

fiber1.resume
# Fiber1: start
# Fiber2: start
# Fiber1: end
# Fiber2: end

Enumerator Fiber

enum = Enumerator.new do |y|
  3.times { |i| y << i * 10 }
end

puts enum.next
puts enum.next
puts enum.next
# 0
# 10
# 20

Generator Pattern

def fibonacci
  Fiber.new do
    a, b = 0, 1
    loop do
      Fiber.yield a
      a, b = b, a + b
    end
  end
end

fib = fibonacci
10.times { puts fib.resume }
# 0 1 1 2 3 5 8 13 21 34

Scheduler Pattern

def run_scheduler(fibers)
  fibers.each(&:resume)
  while fibers.any?(&:alive?)
    fibers.each { |f| f.resume if f.alive? }
  end
end

f1 = Fiber.new { 3.times { puts "F1"; Fiber.yield } }
f2 = Fiber.new { 3.times { puts "F2"; Fiber.yield } }

run_scheduler([f1, f2])
# F1 F2 F1 F2 F1 F2

Common Mistakes

1. Resuming Dead Fiber

fiber = Fiber.new { "done" }
fiber.resume
fiber.resume  # FiberError: dead fiber called

2. Yielding from Main

Fiber.yield  # FiberError: can't yield from main

3. Not Handling FiberError

fiber = Fiber.new { Fiber.yield; "done" }
fiber.resume
fiber.resume
fiber.resume  # FiberError

4. Mixing resume and transfer

Once you use transfer, resume may not work as expected. Stick to one pattern.

5. Blocking in a Fiber

fiber = Fiber.new { sleep 5 }
fiber.resume  # Blocks the entire thread

Practice Questions

1. What is the difference between resume and transfer? resume returns to the resumer; transfer switches control to the target fiber without returning.

2. Can Fibers run in parallel? No, fibers are cooperative. Only one runs at a time within a thread.

3. How are Enumerators related to Fibers? Enumerators use fibers internally to implement lazy iteration.

4. When should I use Fibers over Threads? Fibers for lightweight I/O and cooperative scheduling. Threads for blocking I/O.

Challenge: Build a cooperative multitasking scheduler running 3 fibers that each count to 5.

Solution
fibers = 3.times.map do |id|
  Fiber.new do
    5.times do |i|
      puts "Task #{id}: step #{i + 1}"
      Fiber.yield
    end
  end
end

while fibers.any?(&:alive?)
  fibers.each { |f| f.resume if f.alive? }
end

FAQ

{{< faq question="What is the difference between Fiber and Thread?" >}} Threads are preemptive (OS-scheduled). Fibers are cooperative (explicit yield). Fibers are lighter weight and give you explicit control over scheduling. {{< /faq >}}

{{< faq question="What is Fiber.transfer used for?" >}} Transfer enables asymmetric fibers. It switches to another fiber without returning, allowing complex control flow like coroutines. {{< /faq >}}

{{< faq question="Can Fibers be used with the Async gem?" >}} Yes, the async gem uses fibers internally for non-blocking I/O. It's the foundation of the Async ecosystem in Ruby. {{< /faq >}}

{{< faq question="Are Fibers thread-safe?" >}} No, fibers belong to a single thread. Never share a fiber across threads. Each thread has its own scheduler context. {{< /faq >}}

{{< faq question="What is FiberError?" >}} Raised when you try to resume a dead fiber, yield from main, or perform invalid fiber operations. Always handle it gracefully. {{< /faq >}}

Try It Yourself

counter = Fiber.new do
  i = 0
  loop { i += 1; Fiber.yield i }
end

puts counter.resume
puts counter.resume
puts counter.resume
puts counter.resume

Expected output:

1
2
3
4

What's Next

Now that you understand fibers, explore Ractors for true parallel execution in Ruby.

Topic Description Link
Ruby Ractors Parallel execution without GIL {{< ref "42-ractors" >}}
Ruby Threads Concurrent execution basics {{< ref "40-threads" >}}
Go Goroutines Compare Go's concurrency model Go

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro