スレッド間で繰り返し同期を取る必要がある場合、今までは AutoResetEvent を使っていたが、こちらの記事に「CountdownEvent の方が、オーバーヘッドが小さい」と書かれていた。

そうだったのか!

昔どこかのサンプルコードを見て AutoResetEvent を使っていたけど、他の手段との比較検討はしたことなかったなぁと思い、実際に CountdownEvent と処理速度を比べてみた。

処理速度の結論

処理速度について結論から言うと、AutoResetEvent より CountdownEvent のほうが速い。

そもそも

同期に使えるクラスはたくさんあり、MS 公式にまとめられている。そのうち、
  • AutoResetEvent……スレッドをシグナル状態になるまで待機させる。単一の待ちスレッドが解放された後、自動的にリセットされる。
  • CountdownEvent……スレッドをカウントダウンが 0 になるまで待機させる。複数のスレッドからのシグナルで 1 つ以上のスレッドのブロックを解除できる。
とあるので、待機させる側と待機する側のスレッド数は、AutoResetEvent は 1 対 1、CountdownEvent は複数対複数、ということになる。

また、AutoResetEvent はスレッド解放後に自動的にリセットされる(何もしなくても再び待機させられる)ので、セットし忘れてスレッドがスルーしてしまうバグや、セットのタイミングでブロックできないケースが発生するバグの軽減につながり、安心感は高い。

速度比較

機能的な違いは横に置いておいて、処理速度のみに注目して比較。コードは GitHub に上げておいた

メインスレッドとサブスレッドでそれぞれブロックし合うこと 500 万回(メインとサブがそれぞれブロックするので同期回数は 1,000 万回)、その所要時間を計測した。

result こちらの PC 環境で、.NET 5 の自己完結型リリースビルドで実行したところ、所要時間は
  • AutoResetEvent:22,031 ミリ秒
  • CountdownEvent:578 ミリ秒
となり、CountdownEvent のほうが 40 倍近く速いという結果になった。

まとめ

同期回数 1,000 万回で 20 秒程度の差が出たので、100 万回でも秒単位の差が出ることになる。短時間で 100 万回レベルの同期を行う場合は、CountdownEvent を選択する方が良いと思う。

自分のプロジェクトだとそこまで頻繁に同期するわけではなく(数分間で 1 万回程度、つまり 20 ミリ秒程度の差異)、速度差は誤差程度なので、安心感のある AutoResetEvent を使い続けるかもしれない。