LoginSignup
8
4

More than 5 years have passed since last update.

行儀良い待機でしょうか。いえ、ただのビジーウェイトです

Posted at

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コアをフル回転させる程度に爆走してしまいます。

まとめ

  • タイミングが大切ならきちんとイベントとして仕掛けよう
  • 待機ならウェイトを挟んで時折、で充分
  • 全力を挙げて変化を待ち続ける、なんてことはふつう必要にならない

関連記事


  1. そして、シングルスレッドであるブラウザやNode.jsでは、無限ループがまわり続ける限り別なコードが実行されることはありません。 

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4