8

JesseStorimerの優れた本「WorkingwithUnixProcesses」を読んでます。終了した子プロセスからのシグナルのトラップに関するセクションで、彼はコードサンプルを提供します。

そのコードを少し変更して(以下を参照)、何が起こっているのかをもう少しわかりやすくしました。

  • 親がシグナル間で自身の実行を再開すること(私はそれで見ることができますputs)、
  • wait1つのステートメントで複数の子に対して実行されることtrap(「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
4

1 に答える 1

14

私はRubyソースを調べて、その特定のエラーが発生する場所を確認しました。現在のスレッドがロックを取得しようとしたときにのみ発生しますが、同じロックが現在のスレッドによってすでに取得されています。これは、ロックが再入可能ではないことを意味します。

m = Mutex.new
m.lock
m.lock #=> same error as yours

少なくとも、何が起こるかはわかっていますが、なぜ、どこで起こるのかはまだわかりません。エラーメッセージは、への呼び出し中に発生したことを示していますputs。呼び出されると、最終的にio_binwriteになります。stdoutは同期されませんが、バッファリングされるため、最初の呼び出しで条件が満たされた場合、バッファとそのバッファの書き込みロックが設定されます。書き込みロックは、stdoutへの書き込みのアトミック性を保証するために重要です。2つのスレッドが同時に書き込みを行っstdoutて、互いの出力を混同することはありません。私が何を意味するかを示すために:

t1 = Thread.new { 100.times { print "aaaaa" } }
t2 = Thread.new { 100.times { print "bbbbb" } }
t1.join
t2.join

両方のスレッドが順番にへの書き込みをstdout行いますが、1回の書き込みが分割されることは決してありません。常に完全な5つのaまたはbが順番にあります。これが、書き込みロックの目的です。

さて、あなたの場合にうまくいかないのは、その書き込みロックの競合状態です。親プロセスはループし、stdout毎秒書き込みます(「親は一生懸命働いています」)。ただし、同じスレッドが最終的にtrapブロックを実行し、書き込みを再試行しますstdout(「CHLD信号を受信しました」)。ステートメントを追加することで、それが本当に同じスレッドであることを確認でき#{Thread.current}ます。putsこれらの2つのイベントが十分に接近して発生した場合、最初の例と同じ状況になります。同じスレッドが同じロックを2回取得しようとし、これが最終的にエラーをトリガーします。

于 2012-05-18T22:30:30.833 に答える