[英]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 准備好或休眠時間已過)。
所以,總結一下:
Kernel#sleep
一段duration
。Kernel#sleep
以相同的duration
調用調度程序的#kernel_sleep
鈎子。Fiber.yield
將控制權交給其他光纖”(引用自那里的文檔)希望這可以幫助!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.