为何自定义简易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
相关产品推荐
相关产品推荐

