シェルの非同期コマンドとプロセス ID に関する問題
シェルスクリプトで実行した複数の非同期コマンドのプロセス ID についてかなり面倒な問題に気付いた。
POSIX 規格の wait コマンドの説明に以下のようなスクリプトが載っている。
j1& p1=$! j2& wait $p1 echo Job 1 exited with status $? wait $! echo Job 2 exited with status $?
一見何の変哲もないスクリプトだ ($!
の意味が分からない人はシェルでバックグラウンドで起動したプロセスの戻り値を取得するにはを読むこと)。しかしこのスクリプトはシェルの実装によってはうまく動かない可能性がある。
このスクリプトでは以下の四つの動作を行っている。
- コマンド
j1
を非同期で起動する。 - コマンド
j2
を非同期で起動する。 - コマンド
j1
の終了を待ち、終了ステータスを表示する。 - コマンド
j2
の終了を待ち、終了ステータスを表示する。
問題は、コマンドが終了するのを wait
コマンドで待つ際に、引数としてコマンドのプロセス ID を指定していることだ。ここでもし j1
と j2
のプロセス ID が同じければどうなるか。j1
を待つつもりが j2
を待ってしまうということになりかねない。
二つのプロセス ID が同じくなるシナリオとしては、j1
を起動した後 j2
を起動するまでに j1
が終了しシェルが j1
に対して wait((p)id)
を行った場合に、プロセス ID の再利用によって同じくなる可能性がある。このスクリプトでは j1
を起動した直後に j2
を起動しているのでプロセス ID が同じくなる確率は非常に低いが、j1
と j2
との間で他に色色な処理を行って時間がかかる場合にはプロセス ID が同じくなってもおかしくない。
今時の普通のシェルは非同期で実行したコマンドが終了したらすぐに wait((p)id)
を行う実装になっているので、上に述べたような問題は実際に起こりえる。しかも、問題が発生する確率は高高数パーセントなのでたまにスクリプトが動かないことがある
というだけでプログラマは原因が良く判らぬまま悩み続けることになる。まさかプロセス ID が重複しているとは誰も思うまい。
さて、この問題の責任はどこにあるのか。POSIX 規格は非同期コマンドのプロセス ID は別別でないといけないとは定めていないので、プロセス ID が常に異なるという誤った仮定をしているスクリプトに問題があるということになろうか。しかし実際この仮定が誤っているとは普通のプログラマは気付かないものだし、仮に気付いたとしてもどうしようもない。wait
コマンドではプロセス ID でしか待ち対象のプロセスを区別できないのだから。
となるとシェルの方が何とかしなければならないのだろうか。プロセス ID が同じくなることが問題なので、同じくならないようにすれば万事解決である。その為の唯一の方法は、コマンドが終了しても直ぐに wait((p)id)
を行わないことだ。シェルが wait((p)id)
を行わないうちは、終了したプロセスは未だ成仏していないので、他のプロセスにプロセス ID を使われることはなくなる。しかしこの方法は、スクリプト内で wait
コマンドが実行されるまで未成仏のプロセス (いわゆるゾンビプロセス) が居残り続けるという新たな問題を生む。起動する非同期コマンドの数が少なければそれほど問題ではないが、何百も非同期コマンドをぽんぽん起動するようなスクリプトでは大問題となる (システムがゾンビプロセスで溢れかえって普通のプロセスが起動できなくなる虞がある)。
結局、この問題に対する良い解決策はないのだろうか……。
| 固定リンク
コメント