2022/06/05
Unix Processes
Communicating with Process.wait2
Process.wait2
は子プロセスのpidとexit codeを含むstatusを返す。
irb(main):024:1* def use_process_wait2 irb(main):025:2* 5.times do irb(main):026:3* fork do irb(main):027:3* random_number = rand(5) irb(main):028:3* puts "random_number: #{random_number}, pid: #{Process.pid}" irb(main):029:3* irb(main):030:4* if random_number.even? irb(main):031:4* Process.exit 111 irb(main):032:4* else irb(main):033:4* Process.exit 112 irb(main):034:3* end irb(main):035:2* end irb(main):036:1* end irb(main):037:1* irb(main):038:2* 5.times do irb(main):039:2* pid, status = Process.wait2 irb(main):040:2* irb(main):041:3* if status.exitstatus == 111 irb(main):042:3* puts "#{pid} enuntered an even number!" irb(main):043:3* else irb(main):044:3* puts "#{pid} encountered an odd number!" irb(main):045:2* end irb(main):046:1* end irb(main):047:0> end => :use_process_wait2 irb(main):049:0> use_process_wait2 random_number: 0, pid: 1331 random_number: 3, pid: 1332 random_number: 4, pid: 1333 random_number: 0, pid: 1334 random_number: 1, pid: 1335 1334 enuntered an even number! 1335 encountered an odd number! 1333 enuntered an even number! 1332 encountered an odd number! 1331 enuntered an even number! => 5
Waiting for Specific Children
Process.waitpid
,Process.waitpid2
により,特定の子プロセスの処理が終わるまで,親プロセスをブロックすることができる。
irb(main):045:1* def process_waitpid2 irb(main):046:2* favorite = fork do irb(main):047:2* Process.exit 77 irb(main):048:1* end irb(main):049:1* irb(main):050:2* middle_child = fork do irb(main):051:2* abort "I want to be wanted on!" irb(main):052:1* end irb(main):053:1* irb(main):054:1* pid, status = Process.waitpid2 favorite irb(main):055:1* puts status.exitstatus irb(main):056:0> end => :process_waitpid2 irb(main):057:0> process_waitpid2 I want to be wanted on! 77 => nil
Race Conditions
irb(main):001:1* def race_conditions irb(main):002:2* 2.times do irb(main):003:3* fork do irb(main):004:3* puts Process.pid irb(main):005:2* end irb(main):006:1* end irb(main):007:1* irb(main):008:1* puts Process.wait irb(main):009:1* sleep 5 irb(main):010:1* irb(main):011:1* puts Process.wait irb(main):012:0> end => :race_conditions irb(main):013:0> race_conditions 2766 2767 2766 2767 => nil
上記の例だと,1つ目のProcess.wait
により,2766の子プロセスが終了時まで,親プロセスがブロックされ,そこからsleepが5秒間走るので,先に子プロセスが終了する。しかし,二個目のProcess.wait
では終了した子プロセスのpidを取得できる。これは,Process.wait
は終了した順に子プロセスのpidを取得できるから。
逆に,終了した子プロセスが存在しないときは,エラーとなる。
irb(main):034:0> Process.wait (irb):34:in `wait': No child processes (Errno::ECHILD)
Unicorn
fork型のプロセスはWebサーバーのUnicornで使用されている。親プロセスから複数の子プロセスがforkして作られ,並列性と信頼性が保証される。
Zombie Processes
Process.wait
が呼び出されるまで,子プロセスのstatus情報が残り続ける。Process.detach
によりこの情報を解放することができる。
irb(main):001:1* pid = fork do irb(main):002:1* puts "Child Process" irb(main):003:0> end Child Process => 3157 irb(main):004:0> Process.detach pid => #<Process::Waiter:0x0000000111408780 run> irb(main):005:0> Process.wait (irb):5:in `wait': No child processes (Errno::ECHILD)
Process.detach pid
により,子プロセスのstatus情報が無くなっているため,Process.wait
がエラーとなる。
Trapping SIGCHLD
trap(:CHLD)
ブロック内の処理は,子プロセスが終了したときに行われる。
irb(main):023:1* def trap_sigchld irb(main):024:1* child_processes = 3 irb(main):025:1* dead_processes = 0 irb(main):026:1* irb(main):027:2* child_processes.times do irb(main):028:3* fork do irb(main):029:3* sleep 3 irb(main):030:2* end irb(main):031:1* end irb(main):032:1* irb(main):033:2* trap(:CHLD) do irb(main):034:2* puts Process.wait irb(main):035:2* dead_processes += 1 irb(main):036:2* exit if dead_processes == child_processes irb(main):037:1* end irb(main):038:1* irb(main):039:2* loop do irb(main):040:2* puts "Parent Process" irb(main):041:2* sleep 2 irb(main):042:1* end irb(main):043:0> end => :trap_sigchld irb(main):044:0> trap_sigchld Parent Process Parent Process 3921 3922 3923
3つの子プロセスがforkされ,親プロセスの処理が走りつつも,子プロセスが終了したタイミングでtrap
ブロック内の処理が走る。
SIGCHLD and Concurrency
複数の子プロセスが同時に終了したとき,それらのプロセスが終了したことを見逃してしまうことがある。
補足
Rubyは標準出力をバッファするため,以下の設定で,標準出力がバッファリングされなくなる。
$stdout.sync = true
補足終了。
子プロセスの終了を見逃さない実装は以下となる。
irb(main):001:1* def catch_all_child_processes_deaths irb(main):002:1* child_processes = 3 irb(main):003:1* dead_processes = 0 irb(main):004:1* irb(main):005:2* child_processes.times do |i| irb(main):006:3* fork do irb(main):007:3* sleep 3*i irb(main):008:2* end irb(main):009:1* end irb(main):010:1* irb(main):011:1* $stdout.sync = true irb(main):012:1* irb(main):013:2* trap(:CHLD) do irb(main):014:3* begin irb(main):015:4* while pid = Process.wait(-1, Process::WNOHANG) irb(main):016:4* puts pid irb(main):017:4* dead_processes += 1 irb(main):018:3* end irb(main):019:3* rescue Errno::ECHILD irb(main):020:2* end irb(main):021:1* end irb(main):022:1* irb(main):023:2* loop do irb(main):024:2* exit if dead_processes == child_processes irb(main):025:2* puts 1 irb(main):026:2* sleep 1 irb(main):027:1* end irb(main):028:0> end => :catch_all_child_processes_deaths irb(main):029:0> catch_all_child_processes_deaths irb(main):029:0> catch_all_child_processes_deaths 1 4578 1 1 1 4579 1 1 1 4580
上記のように,子プロセスが終了するたびに,trap
ブロック内の処理が行われ,終了していない子プロセスが存在していても,メインプログラムが実行される。逆に,Process.wait
に第二引数を渡さない以下の方法では,終了していない子プロセスが存在する限り,メインプログラムは実行されない。
irb(main):030:1* def catch_all_child_processes_deaths irb(main):031:1* child_processes = 3 irb(main):032:1* dead_processes = 0 irb(main):033:1* irb(main):034:2* child_processes.times do |i| irb(main):035:3* fork do irb(main):036:3* sleep 3*i irb(main):037:2* end irb(main):038:1* end irb(main):039:1* irb(main):040:1* $stdout.sync = true irb(main):041:1* irb(main):042:2* trap(:CHLD) do irb(main):043:3* begin irb(main):044:4* while pid = Process.wait irb(main):045:4* puts pid irb(main):046:4* dead_processes += 1 irb(main):047:3* end irb(main):048:3* rescue Errno::ECHILD irb(main):049:2* end irb(main):050:1* end irb(main):051:1* irb(main):052:2* loop do irb(main):053:2* exit if dead_processes == child_processes irb(main):054:2* puts 1 irb(main):055:2* sleep 1 irb(main):056:1* end irb(main):057:0> end => :catch_all_child_processes_deaths irb(main):058:0> catch_all_child_processes_deaths 1 4626 4627 4628