簡體   English   中英

是什么導致我的Ruby`track`塊中出現這種死鎖?

[英]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.

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