ruby - 为什么在没有设置调度程序的情况下顺序运行的 Ruby 纤程在设置调度程序时会同时运行?

标签 ruby concurrency fibers ruby-3

我有以下 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 安装的 Ruby 3.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

为什么我的纤程仅在设置调度程序后同时运行?一些较旧的 Stack Overflow 答案(如 this one )指出,纤程是用于流量控制的构造,而不是并发,并且不可能使用纤程编写并发代码。我的结果似乎与此相矛盾。

到目前为止,我对纤程的理解是它们用于协作并发,而不是抢占式并发。因此,为了获得并发性,您需要让它们尽早屈服于其他代码(例如,当 IO 开始时),以便在纤程等待下一个代码时可以执行其他代码执行的机会。

基于这种理解,我想我明白为什么我的代码没有调度程序无法并发运行。它处于 sleep 状态,并且由于它在其中的代码之前和之后缺少 yield 语句,因此它无法及时将控制权交给我编写的任何其他代码。但是当我添加一个调度程序时,它似乎以某种方式屈服于某些东西sleep 是否检测到调度程序并屈服于它,以便我的恢复光纤的代码立即屈服,使其能够立即恢复所有五个光纤?

最佳答案

好问题!

正如 @stefan 上面指出的,Ruby 3.0 引入了“非阻塞光纤”的概念。实际非阻塞行为的实现方式由调度程序实现决定。据我所知,没有默认的调度程序;根据Ruby docs :

If Fiber.scheduler is not set in the current thread, blocking and non-blocking fibers’ behavior is identical.

现在,回答你的最后一个问题:

But when I add a scheduler, it appears to somehow yield to something ... Is sleep detecting the scheduler and yielding to it so that my code resuming the fibers is immediately yielded to, making it able to immediately resume all five fibers?

你正在做某事!当您设置光纤调度程序时,它应该符合 Fiber::SchedulerInterface ,它定义了几个“钩子(Hook)”。其中一个钩子(Hook)是 #kernel_sleepKernel#sleep(和 Mutex#sleep)调用!

我不能说我读过很多 libev 代码,但你可以找到该钩子(Hook)的 libev_scheduler 实现 here .

这个想法是(强调我自己的):

The scheduler runs into a wait loop, checking all the blocked fibers (which it has registered on hook calls) and resuming them when the awaited resource is ready (e.g. I/O ready or sleep time elapsed).

所以,总结一下:

  1. 您的 Fiber 在一段持续时间内调用 Kernel#sleep
  2. Kernel#sleep 以相同的持续时间调用调度程序的 #kernel_sleep Hook 。
  3. 调度“以某种方式注册当前光纤正在等待的内容,并使用Fiber.yield将控制权交给其他光纤”(引自那里的文档)
  4. “调度程序进入等待循环,检查所有阻塞的纤程(它已在钩子(Hook)调用中注册)并在等待的资源准备就绪时恢复它们(例如 I/O 就绪或 sleep 时间已过)。”<

希望这有帮助!

关于ruby - 为什么在没有设置调度程序的情况下顺序运行的 Ruby 纤程在设置调度程序时会同时运行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73723382/

相关文章:

ruby-on-rails - 使用 ActiveRecord 在数据库中存储数组

java - java jdk 1.7中的LinkedBlockingQueue

java - 使用多线程处理 'N' 项的列表

wcf - Silverlight并发WCF

c# - 在 CLR 中使用托管线程和纤程

Ruby 1.9.3-p140 - 使用线程 - 如何等待所有结果从线程中出来?

javascript - 什么是 Meteor 并发模型?

ruby - 如何将此算法从 Ruby 转换为 Clojure?

Ruby Capybara Element not found 错误处理

ruby-on-rails - Ruby Gem 随机返回编码错误