如何非阻塞恢复Ruby Fiber?如何实现多Fiber并发运行?
Ruby Fiber 相关技术问题解答
问题说明
我有两个关于Ruby Fiber的技术问题:
- 如何以非阻塞方式恢复Fiber?
- 如何让两个Fiber实现并发运行?
当前的测试代码是串行执行两个包含sleep操作的Fiber,总耗时约2秒:
f1 = Fiber.new { sleep 1 } f2 = Fiber.new { sleep 1 } start = Time.now f1.resume f2.resume p (Time.now - start).round # => 2 # 期望改写成类似这样的逻辑: # f1.non_blocking_start # f2.non_blocking_start # f1.blocking_await # f2.blocking_await
在JavaScript(TypeScript)中,可以通过以下代码实现并发效果(总耗时约1秒),由于Ruby Fiber和JS Async原理相近,希望了解Ruby中的实现方式:
function sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(() => resolve())) } let f1 = async function(): Promise<void> { await sleep(1000) } let f2 = async function(): Promise<void> { await sleep(1000) } async function main() { let p1 = f1() let p2 = f2() await p1 await p2 } main()
问题解答
1. 非阻塞恢复Fiber的实现方式
Ruby原生的Fiber.resume是阻塞式的——当Fiber执行到诸如sleep这类阻塞操作时,会卡住整个线程。要实现非阻塞恢复,核心是结合非阻塞IO与Fiber调度器:让Fiber在遇到阻塞操作时主动让出执行权,由调度器在IO操作完成后再恢复该Fiber的执行。
从Ruby 3.0开始,标准库提供了Fiber.scheduler接口,可用于实现用户态的Fiber调度;也可以使用第三方的async gem简化开发。
2. 让两个Fiber并发运行的具体实现
Fiber是协作式多任务模型,需要调度器来管理Fiber的切换——当一个Fiber进入阻塞状态时,调度器自动切换到其他可运行的Fiber,从而实现“并发”效果。
方式一:使用Ruby 3.0+ 内置调度器
# 封装非阻塞sleep,基于内置调度器实现 def sleep_non_blocking(seconds) Fiber.scheduler.wait(Process.clock_gettime(Process::CLOCK_MONOTONIC) + seconds) end # 创建包含非阻塞操作的Fiber f1 = Fiber.new do puts "Fiber 1 启动" sleep_non_blocking(1) puts "Fiber 1 完成" end f2 = Fiber.new do puts "Fiber 2 启动" sleep_non_blocking(1) puts "Fiber 2 完成" end start = Time.now # 启用调度器,批量运行Fiber Fiber.set_scheduler(Fiber::Scheduler.new) Fiber.scheduler do f1.resume f2.resume end puts "总耗时: #{(Time.now - start).round}秒" # 输出:1
方式二:使用async gem(更简洁)
先安装gem:
gem install async
实现代码:
require 'async' def sleep_non_blocking(seconds) Async::Task.current.sleep(seconds) end Async do |root_task| start = Time.now # 并发启动两个任务(底层基于Fiber) task1 = root_task.async do puts "Fiber 1 启动" sleep_non_blocking(1) puts "Fiber 1 完成" end task2 = root_task.async do puts "Fiber 2 启动" sleep_non_blocking(1) puts "Fiber 2 完成" end # 等待两个任务结束 task1.wait task2.wait puts "总耗时: #{(Time.now - start).round}秒" # 输出:1 end
原理对比
JS的Async/Await依赖浏览器/Node.js的事件循环自动调度Promise;而Ruby Fiber需要显式的调度器来管理切换——当Fiber执行非阻塞操作时,调度器会将其挂起,转而执行其他Fiber,直到阻塞操作完成后再恢复,以此实现类似JS的并发效果。
内容的提问来源于stack exchange,提问作者Alex Craft
相关产品推荐
相关产品推荐

