[英]What causes this deadlock in my Ruby `trap` block?
我正在閱讀Jesse Storimer的優秀着作“使用Unix進程” 。 在一個關於從已經退出的子進程捕獲信號的部分中,他給出了一個代碼示例。
我稍微修改了這段代碼(見下文),以便更清楚地了解正在發生的事情:
puts
) wait
在一個trap
語句中為多個孩子執行wait
(有時我得到“收到CHLD信號”一次后跟多個“子pid退出”)。 通常,下面代碼的輸出類似於:
parent is working hard
Received a CHLD signal
child pid 73408 exited
parent is working hard
parent is working hard
parent is working hard
Received a CHLD signal
child pid 73410 exited
child pid 73409 exited
All children exited - parent exiting too.
但有一段時間我得到這樣的錯誤:
trapping_signals.rb:17:in `write': deadlock; recursive locking (ThreadError)
from trapping_signals.rb:17:in `puts'
from trapping_signals.rb:17:in `puts'
from trapping_signals.rb:17:in `block in <main>'
from trapping_signals.rb:17:in `call'
from trapping_signals.rb:17:in `write'
from trapping_signals.rb:17:in `puts'
from trapping_signals.rb:17:in `puts'
from trapping_signals.rb:17:in `block in <main>'
from trapping_signals.rb:40:in `call'
from trapping_signals.rb:40:in `sleep'
from trapping_signals.rb:40:in `block in <main>'
from trapping_signals.rb:38:in `loop'
from trapping_signals.rb:38:in `<main>
任何人都可以向我解釋這里出了什么問題嗎?
child_processes = 3
dead_processes = 0
# We fork 3 child processes.
child_processes.times do
fork do
# Each sleeps between 0 and 5 seconds
sleep rand(5)
end
end
# Our parent process will be busy doing some work.
# But still wants to know when one of its children exits.
# By trapping the :CHLD signal our process will be notified by the kernel
# when one of its children exits.
trap(:CHLD) do
puts "Received a CHLD signal"
# Since Process.wait queues up any data that it has for us we can ask for it
# here, since we know that one of our child processes has exited.
# We loop over a non-blocking Process.wait to ensure that any dead child
# processes are accounted for.
# Here we wait without blocking.
while pid = Process.wait(-1, Process::WNOHANG)
puts "child pid #{pid} exited"
dead_processes += 1
# We exit ourselves once all the child processes are accounted for.
if dead_processes == child_processes
puts "All children exited - parent exiting too."
exit
end
end
end
# Work it.
loop do
puts "parent is working hard"
sleep 1
end
我查看了Ruby源代碼以查看引發該特定錯誤的位置,並且只在當前線程嘗試獲取鎖定時才會引發該錯誤,但當前線程已經采用了相同的鎖定。 這意味着鎖定不是可重入的:
m = Mutex.new
m.lock
m.lock #=> same error as yours
現在至少我們知道會發生什么,但不知道為什么以及在哪里。 錯誤消息表明它在調用puts
期間發生。 當它被調用時,它最終以io_binwrite結束。 stdout
未同步,但它是緩沖的,因此在第一次調用時滿足此條件 ,並且將設置緩沖區加上該緩沖區的寫鎖定。 寫鎖定對於保證寫入stdout的原子性很重要,不應該發生同時寫入stdout
兩個線程混淆了彼此的輸出。 為了證明我的意思:
t1 = Thread.new { 100.times { print "aaaaa" } }
t2 = Thread.new { 100.times { print "bbbbb" } }
t1.join
t2.join
雖然兩個線程輪流寫入stdout
,但是單個寫入被打破絕不會發生 - 你將始終按順序排列完整的5個或b個。 這就是寫鎖的用途 。
現在出現問題的是寫鎖定的競爭條件。 父進程每秒循環並寫入stdout
(“父進程正在努力”)。 但是同一個線程最終也會執行trap
塊並再次嘗試寫入stdout
(“收到CHLD信號”)。 您可以通過在puts
語句中添加#{Thread.current}
來驗證它是否真的是相同的線程。 如果這兩個事件發生得足夠緊密,那么你將遇到與第一個例子相同的情況:同一個線程試圖獲得兩次相同的鎖,這最終會觸發錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.