簡體   English   中英

為什么沒有設置調度器的情況下順序運行的Ruby纖程設置了調度器后會同時運行?

[英]Why do Ruby fibers that run sequentially without a scheduler set run concurrently when a scheduler is set?

我有以下 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運行程序時,我得到以下 output:

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運行程序時,我得到以下 output:

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 回答(例如這個) state 纖維是一種用於流量控制的構造,而不是並發,並且不可能使用纖維來編寫並發代碼。 我的結果似乎與此相矛盾。

到目前為止,我對 Fiber 的理解是它們用於協作並發,而不是搶占式並發。 因此,為了從它們中獲得並發性,您需要讓它們盡可能早地屈服於其他代碼(例如,當 IO 開始時),以便在光纖等待下一個代碼時可以執行其他代碼執行的機會。

基於這種理解,我想我理解了為什么我的代碼沒有調度器就不能同時運行。 它處於休眠狀態,並且由於在其中的代碼之前和之后缺少yield語句,因此它沒有時間點可以讓出控制權給我編寫的任何其他代碼。 但是當我添加一個調度程序時,它似乎以某種方式屈服於某事 sleep是否檢測到調度程序並讓步,以便我的恢復光纖的代碼立即讓步,使其能夠立即恢復所有五個光纖?

好問題!

正如@stefan 上面提到的,Ruby 3.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 准備好或睡眠時間已過)。”

希望這可以幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM