我有以下的Gemfile:
source "https://rubygems.org"
ruby "3.1.2"
gem "libev_scheduler", "~> 0.2"以及名为main.rb的文件中的以下Ruby代码
require 'libev_scheduler'
set_sched = ARGV[0] == "--set-sched"
if set_sched then
Fiber.set_scheduler Libev::Scheduler.new
end
N_FIBERS = 5
fibers = []
N_FIBERS.times do |i|
n = i + 1
fiber = Fiber.new do
puts "Beginning calculation ##{n}..."
sleep 1
end
fibers.push({fiber: fiber, n: n})
end
fibers.each do |fiber|
fiber[:fiber].resume
end
puts "Finished all calculations!"我正在执行通过RVM安装的Ruby3.1.2代码。
当我使用time bundle exec ruby main.rb运行程序时,我得到以下输出:
Beginning calculation #1...
Beginning calculation #2...
Beginning calculation #3...
Beginning calculation #4...
Beginning calculation #5...
Finished all calculations!
real 0m5.179s
user 0m0.146s
sys 0m0.027s当我使用time bundle exec ruby main.rb --set-sched运行程序时,我得到以下输出:
Beginning calculation #1...
Beginning calculation #2...
Beginning calculation #3...
Beginning calculation #4...
Beginning calculation #5...
Finished all calculations!
real 0m1.173s
user 0m0.150s
sys 0m0.021s为什么我的纤维只在我设置了调度程序时才并发运行?一些旧的堆栈溢出回答(如这一个)指出,光纤是用于流控制的结构,而不是并发性,并且不可能使用光纤编写并发代码。我的结果似乎与此相矛盾。
到目前为止,我对光纤的理解是,它们是用于协作并发的,而不是抢占式并发。因此,为了从它们中获取并发性,您需要让它们尽可能早地向其他代码屈服(例如。当IO开始时),以便在光纤等待下一个执行机会时执行其他代码。
基于这种理解,我想我理解为什么没有调度器的代码不能同时运行。它会休眠,因为它在代码中的前后都缺少yield语句,所以在任何其他我编写的代码中都没有时间可以控制它。但是当我添加一个调度程序时,它似乎在某种程度上屈服于某种东西。sleep是否检测到调度程序并屈服于它,以便我的代码立即恢复纤维,使其能够立即恢复所有五个光纤?
发布于 2022-09-15 11:33:01
问得好!
正如@stefan上面所指出的,Ruby3.0引入了“非阻塞光纤”的概念。完成实际的非阻塞行为的方式由调度器实现。据我所知,没有默认的调度程序;根据Ruby文档
如果在当前线程中没有设置
Fiber.scheduler,则阻塞和非阻塞光纤的行为是相同的。
现在,回答你的最后一个问题:
但当我添加调度程序时,它似乎会屈服于某种东西.睡眠是否检测调度程序并屈服于它,以便我的代码立即恢复纤维,使它能够立即恢复所有五个纤维?
你发现什么了!当您设置一个光纤调度程序时,它应该符合Fiber::SchedulerInterface,后者定义了几个“钩子”。其中一个钩子是#kernel_sleep,它由Kernel#sleep (和Mutex#sleep)调用!
我不能说我读过很多libev代码,但是您可以找到libev_scheduler的钩子这里的实现。
这个想法是(强调我自己的):
调度程序进入等待循环,检查所有阻塞的纤维(它在钩子调用时注册了),并在等待的资源准备好时(例如I/O就绪或休眠时间流逝)恢复它们。
因此,总括而言:
Kernel#sleep调用duration。Kernel#sleep用相同的duration调用调度程序的#kernel_sleep挂钩。Fiber.yield生成对其他光纤的控制”(引用那里的文档)希望这能有所帮助!
https://stackoverflow.com/questions/73723382
复制相似问题