首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么在设置调度程序时,在没有调度程序集的情况下按顺序运行的Ruby纤维会同时运行?

为什么在设置调度程序时,在没有调度程序集的情况下按顺序运行的Ruby纤维会同时运行?
EN

Stack Overflow用户
提问于 2022-09-14 21:32:59
回答 1查看 75关注 0票数 0

我有以下的Gemfile:

代码语言:javascript
复制
source "https://rubygems.org"

ruby "3.1.2"

gem "libev_scheduler", "~> 0.2"

以及名为main.rb的文件中的以下Ruby代码

代码语言:javascript
复制
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运行程序时,我得到以下输出:

代码语言:javascript
复制
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运行程序时,我得到以下输出:

代码语言:javascript
复制
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是否检测到调度程序并屈服于它,以便我的代码立即恢复纤维,使其能够立即恢复所有五个光纤?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 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就绪或休眠时间流逝)恢复它们。

因此,总括而言:

  1. 您的光纤使用一些Kernel#sleep调用duration
  2. Kernel#sleep用相同的duration调用调度程序的#kernel_sleep挂钩。
  3. 该计划“以某种方式记录当前光纤正在等待的内容,并使用Fiber.yield生成对其他光纤的控制”(引用那里的文档)
  4. 调度程序进入等待循环,检查所有阻塞的纤维(它已在钩子调用中注册),并在等待的资源准备就绪时恢复它们(例如I/O就绪或经过睡眠时间)。

希望这能有所帮助!

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73723382

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档