Node.jsで非同期実行の待機をしていたら、思わぬ事態に見舞われてしまいました。
起こった問題
Node.jsで終了処理を行うために、SIGTERM
にイベントを仕掛けていました。そして、終わったことが確認されてからプロセスを落とす予定でした。
process.on('SIGTERM', function checker(){
if(/* 終了条件が満たされていない */){
process.nextTick(checker);
return;
}
process.exit();
});
このようにした上で実際にSIGTERM
を送ってみたところ、仮想マシン全体が不調となってしまいました。top
で見てみると、終了前のNode.jsプロセスが95%以上のCPUを消費してしまっていました。
「非同期」の中身
無限ループさせながら待機するのは、直感的にも「まずい」とわかると思いますが1、今回のように非同期で流れを切ってもCPUを食ってしまったのはなぜなのでしょうか。
まず、ここでの「終了条件」のチェックはすぐ終わるので、nextTick
の登録が終わればすぐに抜けますが、抜けた直後にnextTick
の関数が動き出す、という流れでハイペースでまわり続けます。さらに悪い事に、nextTick
はI/O処理より前に実行されるので、一度実行ループに入ってしまえば、抜けるチャンスはなくなってしまいます。
正しい書き方
非同期実行でいつ発生するかわからないイベントを、1ミリ秒の遅延もなく待ちたいというのであれば、イベントに直接ハンドラを仕掛けるのが適当でしょうし、終了処理のような場合は(状況にもよりますが)1秒遅れても実害がない、というケースも多いでしょう。
そういう、時間的余裕があるタスクに対して、全力でCPUを使いながら監視する必要性は全くありません。ブラウザにもあるsetTimeout
を使って、時々実行させるような形にすればそれで十分です。
process.on('SIGTERM', function checker(){
if(/* 終了条件が満たされていない */){
setTimeout(checker, 100);
return;
}
process.exit();
});
たとえ1ミリ秒しかウェイトを入れなかったとしても、現代の数ギガヘルツのCPUにしてみれば、それは数百万クロックという途方もない時間です。それだけの時間を処理し続けるのと、単に待つのとでは、負荷の違いは明らかです。
ブラウザ環境では?
ブラウザの場合、setTimeout
を使っている限りは、0にしても数ミリ秒のウェイトが入るので、激しく実行されるようなことはありません。一方で、postMessage
を使えばウェイトなしで非同期実行できますが、こちらの方はそのまま回し続けると、CPUの1コアをフル回転させる程度に爆走してしまいます。
まとめ
- タイミングが大切ならきちんとイベントとして仕掛けよう
- 待機ならウェイトを挟んで時折、で充分
- 全力を挙げて変化を待ち続ける、なんてことはふつう必要にならない
関連記事
-
そして、シングルスレッドであるブラウザやNode.jsでは、無限ループがまわり続ける限り別なコードが実行されることはありません。 ↩