You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何自定义简易Ruby Fiber调度器时sleep无延迟?

Ruby Fiber交织并发调度器实现问题

我尝试用Ruby Fibers实现「交织式」并发,自己写了调度器但出问题了。

测试代码

f1 = Fiber.new do
  puts "1: start"
  sleep 2
  puts "1: almost" 
  sleep 2
  puts "1: done"
end.resume

f2 = Fiber.new do
  puts "2: start"
  sleep 2
  puts "2: almost" 
  sleep 2
  puts "2: done"
end.resume

预期与实际结果

预期行为:

  • 无调度器时,Fiber顺序执行,总耗时8秒;
  • 有调度器时,IO(sleep)时切换控制权,Fiber并行等待,总耗时约4秒。

实际结果:
启用调度器后,Fiber完全没有休眠,瞬间执行完成:

time SCHEDULER=yes ruby fiber-non-blocking-io.rb
1: start
1: almost
1: done
2: start
2: almost
2: done
SCHEDULER=yes ruby fiber-non-blocking-io.rb  0.05s user 0.03s system 99% cpu 0.083 total

自定义调度器代码

class S
  def initialize
    @fibers = []    
  end

  def block(blocker, timeout = nil) 
    fiber = Fiber.current
    puts "block", blocker.inspect, timeout.inspect, fiber.inspect
    @fibers << fiber
  end

  def unblock(blocker, fiber) 
    puts "unblock", blocker.inspect, fiber.inspect
    @fibers.reject!{ _1 == fiber} # remove the fiber
    fiber.resume
  end

  def fiber(&block)
    Fiber.new(blocking: false, &block).tap(&:resume)
  end

  def kernel_sleep(duration = nil) 
    @fibers << Fiber.current
  end

  def io_wait(io, events, timeout) 
    puts "io_wait not implemented"
  end
end

调度器启用代码

Fiber.set_scheduler(S.new) if ENV['SCHEDULER'] == 'yes'

疑问

  • 为什么sleep完全没生效?我的认知模型是:Fiber执行到sleep时,调度器的kernel_sleep被调用,把Fiber存起来,之后应该检查休眠时间到了再恢复,但我还没实现这部分,可也不该直接跳过休眠啊?
  • 调度器里的puts完全没输出,明明调度器起作用了(行为变了),但看不到日志,没法排查。

问题根源与修复方案

1. sleep未生效的原因

Ruby的sleep在有调度器时会调用调度器的kernel_sleep方法,但你的实现只是把Fiber加入数组,既没有主动交出控制权(Fiber.yield),也没有模拟休眠逻辑。这导致Fiber直接跳过sleep继续执行,因为调度器没告诉它要暂停。

正确的kernel_sleep实现需要:

  • 记录Fiber的休眠结束时间
  • 调用Fiber.yield交出控制权,让调度器有机会切换到其他Fiber
  • 调度器需要在主循环中检查休眠的Fiber是否到时间,恢复执行

2. 调度器日志没输出的原因

你创建Fiber的方式不对:直接用Fiber.new而不是调度器的fiber方法。手动调用Fiber.new.resume创建的是**阻塞模式(blocking: true)**的Fiber,不会触发调度器的回调。必须通过调度器的fiber方法创建非阻塞Fiber,才能让调度器接管IO操作。

修复后的代码示例

修改测试代码:用调度器创建并管理Fiber

if ENV['SCHEDULER'] == 'yes'
  scheduler = S.new
  Fiber.set_scheduler(scheduler)
  # 用调度器的fiber方法创建Fiber
  scheduler.fiber do
    puts "1: start"
    sleep 2
    puts "1: almost" 
    sleep 2
    puts "1: done"
  end.resume
  scheduler.fiber do
    puts "2: start"
    sleep 2
    puts "2: almost" 
    sleep 2
    puts "2: done"
  end.resume
  # 启动调度器主循环
  scheduler.run
else
  # 原测试代码
  f1 = Fiber.new do
    puts "1: start"
    sleep 2
    puts "1: almost" 
    sleep 2
    puts "1: done"
  end.resume

  f2 = Fiber.new do
    puts "2: start"
    sleep 2
    puts "2: almost" 
    sleep 2
    puts "2: done"
  end.resume
end

修改调度器:添加休眠时间记录和主循环逻辑

class S
  def initialize
    @sleeping_fibers = [] # 存储[休眠结束时间, Fiber]
  end

  def block(blocker, timeout = nil) 
    fiber = Fiber.current
    puts "block: #{blocker.inspect}, timeout: #{timeout.inspect}, fiber: #{fiber.inspect}"
    Fiber.yield
  end

  def unblock(blocker, fiber) 
    puts "unblock: #{blocker.inspect}, fiber: #{fiber.inspect}"
    fiber.resume
  end

  def fiber(&block)
    Fiber.new(blocking: false, &block)
  end

  def kernel_sleep(duration = nil) 
    duration ||= 0
    fiber = Fiber.current
    puts "kernel_sleep: #{duration}s, fiber: #{fiber.inspect}"
    # 记录休眠结束时间
    wakeup_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
    @sleeping_fibers << [wakeup_time, fiber]
    # 交出控制权
    Fiber.yield
  end

  def io_wait(io, events, timeout) 
    puts "io_wait not implemented"
    Fiber.yield
  end

  # 调度器主循环:处理休眠的Fiber
  def run
    while !@sleeping_fibers.empty?
      # 按唤醒时间排序
      @sleeping_fibers.sort_by! { |time, _| time }
      earliest_time, _ = @sleeping_fibers.first
      now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
      sleep_duration = earliest_time - now
      # 主线程休眠到最早唤醒时间
      sleep(sleep_duration) if sleep_duration > 0
      # 唤醒所有到时间的Fiber
      now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
      ready_fibers = @sleeping_fibers.select { |time, _| time <= now }
      ready_fibers.each do |_, fiber|
        fiber.resume
      end
      # 移除已唤醒的Fiber
      @sleeping_fibers -= ready_fibers
    end
  end
end

修复后效果

启用调度器后,两个Fiber会交织执行,总耗时约4秒,调度器的日志也会正常输出。

内容的提问来源于stack exchange,提问作者Greg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 12:04:54