2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#でのWaitHandle待機スレッド調査

Posted at

調査内容

C#かつWindows向けプログラムではWaitHandleを待ちたいことがある。例えばWaitableTimerの満了待機のために。これはTaskともまた違う設計思想に基づいているように見える。よくわからないので調べた。特に待機時のスレッドについて。

WaitHandleまわり

  • イベントとはシグナル状態、非シグナル状態の2値によるタイミング通知の仕組み。
  • イベント待機にはWaitHandleを使う。待機中はスレッドブロッキングする。
  • イベント操作にはEventWaitHandle/AutoResetEvent/ManualResetEventクラスを使う。
    • できあいのクラスはこれらによる操作機能をすでに内包している。
  • WaitAny/WaitAllはWaitForMultipleObjects関数相当のC#版

参考資料

ブロッキング対象スレッド

WaitHandleの待機中はスレッドがブロッキングする。対象スレッド毎に待機方法をまとめる。

スレッドの分類、生成方法別

No ブロッキング対象スレッド案 どのようなプログラムでブロッキング可能か
1 メインスレッド バッチ処理。
2 ThreadPoolワーカースレッド ThreadPoolが暇なプログラム。並列度が低いもの。
3 ThreadPool I/Oスレッド 対象外とする。これはIOCP用。それ以外の用途にはWaitHandle含め適さない。
4 明示的に生成したスレッド ThreadPoolが忙しいプログラム。並列度が高いもの。

スレッド別、待機実現方法

メインスレッド

  • メインスレッドでWaitHandle.WaitOneメソッドを呼ぶ。

ThreadPoolワーカースレッド

  • Task.RunとWaitHandle.WaitOneメソッドを組み合わせる。
  • ThreadPool.QueueUserWorkItemとWaitHandle.WaitOneメソッドを組み合わせる。
  • ThreadPool.RegisterWaitForSingleObjectメソッドを使う。

明示的に生成したスレッド

  • new Thread(), _beginthread(), pthread_create()などでスレッドを生成して利用する。それらのスレッド中でWaitHandle.WaitOneメソッドを呼ぶ。

なおスレッド生成タイミングや生成数にも複数のやり方がある。WaitHandle1つ毎に都度生成&破棄、起動時に固定数生成、負荷に応じて数を自動調整など。

キャンセル

WaitHandle.WaitOneメソッドは対象が条件を満たすまでずっと待機し続ける。これを中断するには2つの方法がある。

  1. WaitOneメソッドの引数にタイムアウト時間を指定する。
  2. WaitHandle.WaitAllまたはWaitHandle.WaitAnyを使ってその他のWaitHandleとともに待機する。例えばCancellationToken.WaitHandle.

これらは併用もできる。下記の記事に実装コード例有り。

コード: 上記記事から引用、CancellationTokenによるキャンセル。

int eventThatSignaledIndex =
       WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
                          new TimeSpan(0, 0, 20));

必要スレッド数削減

WaitOneは1つのWaitHandle待機につき1スレッドを使ってしまう。

ThreadPool.RegisterWaitForSingleObjectはその内部でWaitForMultipleObjectsを使っているため必要スレッド数が1/64で済むとのこと(※)。自前スレッドプールの場合も同様の対応で必要スレッド数を削減できる。

※参考: https://devblogs.microsoft.com/oldnewthing/20081117-00/?p=20183

WaitForMultipleObjects関数は同時に取り扱い可能なハンドル数が64までなことに注意。これはWaitHandle.WaitAllとWaitHandle.WaitAnyでも同様。

Taskでラッピング

WaitHandleのまま扱うのではなくTaskとして扱う方法もある。

下記の記事にThreadPool.RegisterWaitForSingleObjectをTaskCompletionSourceでラッピングするコード例が載っている。

参考資料

The value of MAXIMUM_WAIT_OBJECTS is 64 (defined in winnt.h),

→ThreadPoolクラスのI/Oスレッドについての解説有り。どうもIOCP: I/O Completion Portのためのスレッドということらしい。それ以外はI/O待ちだろうとなんだろうとThreadPoolのワーカースレッドを使う。

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?