[.NET] Thread.Yield(), Thread.Sleep(0), Thread.Sleep(1)の違い

.NETでは、現在処理中のスレッドのかわりに、他のスレッドに処理を移すための方法がいくつかある。それぞれの違いについて調べた。

# 前置き
[Monitor.Wait/PulseよりもManualResetEventSlimを使う](http://qiita.com/laughter/items/1e64ab8712e410a8f047)の投稿の中で、.NETの`ManualResetEventSlim`はスピンウェイト中に

- `Thread.Yield()`
- `Thread.Sleep(0)`
- `Thread.Sleep(1)`

を呼び出していると書いた。
このコードを読んだとき、驚いて何を調べているか忘れそうになった。

`Thread.Yield()`はわかる。他のスレッドに実行できるものがあれば、現在実行中のスレッドの実行をとりやめて処理を譲る動作をするのだろう。
`Thread.Sleep(0)`もいい。`Thread.Interrupt`されたら`ThreadInterruptedException`例外を投げるとともに、やはり他のスレッドに処理を移す。両方実行しているのは、おそらくは両者で切り替える対象となるスレッドの範囲に差があるからだ。

しかし、`Thread.Sleep(1)`とは。
応答性をあげるためのスピンウェイトの処理の内部では、スレッドの休止時間などいらない。だから、引数には最小の値を指定したいはずだ。
それなのに、引数を`1`と指定している。`Thread.Sleep(0)`と最低1ミリ秒待機する以外に何の差があるというのだろうか。

# `Thread.Yield`の内部
MSDNで`Thread.Yield`を調べると、

> 呼び出し元のスレッドから、現在のプロセッサ上で実行する準備が整っている別のスレッドに実行を切り替えます。

と書かれている。
つまり、**`Thread.Yield()`**によって処理が譲られる対象のスレッドは、あくまで、**現在のプロセッサ上で実行待ちになっている**ものに限られているわけだ。

そしてその内部では、

> このメソッドは、ネイティブの Win32 呼び出しプラットフォームを使用して SwitchToThread 関数。

全然意味がわからないので英語を読むと、

> This method is equivalent to using platform invoke to call the native Win32 SwitchToThread function.

Win32のネイティブな関数である`SwitchToThread`で実装されていることがわかる。
MSDNで`SwitchToThread`を調べてみても、

> 実行の譲渡は呼び出し側スレッドのプロセッサに制限されます。別のプロセッサがアイドルであったり、別のプロセッサで実行中のスレッドの優先順位が低くても、別のプロセッサでの実行に切り替わることはありません。

と書かれており、譲られる対象のスレッドは現在のプロセッサ上で実行待ちになっているものに限られていることに間違いはないようだ。

# `Thread.Sleep`の内部
では、`Thread.Sleep(Int32)`はどうだろうか。

> 指定したミリ秒数の間現在のスレッドを中断します。

ここまではいい。`Thread.Sleep`は現在のスレッドを中断する。

>  millisecondsTimeout 引数の値が 0 である場合は、スレッドは自らのタイム スライスの残りの部分を放棄し、実行する準備ができている同じ優先順位の他のスレッドに渡します。優先順位が同じで実行する準備ができている他のスレッドが存在しない場合は、現在のスレッドの実行は中断されません。 

ただし、**引数に0を指定されたときのみ、実行を他のスレッドに渡さない限り、スレッドは中断されない**。そして、`Thread.Sleep(0)`によって実行を渡される対象のスレッドは、**同じ優先順位の実行準備ができているもの**に限られているらしい。

しかし、`Thread.Sleep`が呼び出していると思われる`Sleep`をMSDNで調べてみると、違うことが書いてあるのだ。

> A value of zero causes the thread to relinquish the remainder of its time slice to any other thread that is ready to run. If there are no other threads ready to run, the function returns immediately, and the thread continues execution.

ここには、**同じ優先順位とは書かれていない**。むしろ、単に**実行準備ができているものが対象になる**と読みとることができる。
そして、

> Windows XP:  A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution. This behavior changed starting with Windows Server 2003.

Windows XPの特殊な動作[^1]として、**同じ優先順位**のスレッドという記述がはじめてでてくる。

どうやら、**`Thread.Sleep(0)`**は、XPとそれ以降とで動作が異なるらしい。**Windows XP[^2]では同じ優先順位の実行待ちのスレッド**に処理を譲り、**Windows Server 2003以降では、実行待ちのスレッド**が譲られる対象になる。

では、引数に0以外を指定したらどうだろう。

> This function causes a thread to relinquish the remainder of its time slice and become unrunnable for an interval based on the value of dwMilliseconds.

引数に0以外を指定した場合、割り当てられていたのこりのタイムスライスを放棄する。そして、引数で指定された期間だけ実行不可になる。

0を指定した場合と異なり、のこりのタイムスライスは代わりに実行されることになるスレッドには渡されないし、その代わりに実行されることになるスレッドを限定する機能もない。
結果的に、処理が回ってくるスレッドはOSのスケジューラ次第だ。

つまり、**`Thread.Sleep(1)`では実行待ちのスレッドが代わりに実行される**ことになる。

# 計測

これらのメソッドを単純に100000回ほど呼んで、単純にかかった時間を計測した。
バックで動いているスレッド次第でいろいろな計測結果になるのだろうが、なんらかの速度の目安にはなるだろう。
環境は、CPU:Intel Core i3-2100 3.10GHz、メモリ:8GB、OS:Windows7 SP1 64bit、開発環境:Visual Studio 2010になる。

|メソッド| 100000回コールにかかった時間(ミリ秒) |
|:------|--------------------------:|
|`Thread.Yield()` | 32              |
|`Thread.Sleep(0)`| 46              |
|`Thread.Sleep(1)`| 100080          |

`Thread.Yield()`は実行を譲渡するスレッドを限定しているだけあってもっとも高速だ。
逆に、`Thread.Sleep(1)`は毎回最低1ミリ秒経過させるだけあってもっとも低速である。

# 結論
処理を他のスレッドに渡すメソッドの違いは以下のようになる。

|メソッド          |処理が渡されるスレッドの種類                                        | 速度 |
|:----------------|:----------------------------------------------------------------|:---:|
|`Thread.Yield()` |現在のプロセッサ上で実行待ちになっているスレッド                       | 最速 |
|`Thread.Sleep(0)`|実行待ちのスレッド。ただし、WindowsXPとそれ以前の場合、同じ優先順位に限定される| 中 |
|`Thread.Sleep(1)`|実行待ちのスレッド                | 遅 |

もし、処理を渡すスレッドの範囲を広げたいのであれば`Thread.Sleep(0)`を呼ぶべきだ。ただし、Windows XPとそれ以前のことを考慮するのであれば、速度と引き換えに`Thread.Sleep(1)`を使うことになるだろう。
逆に、かわりに実行されるスレッドが限定されたとしても、すみやかに処理を渡す必要があるのであれば`Thread.Yield()`を使用する。

.NET Framework4.5以上の環境では、Windows XPとそれ以前のOSはサポートされないため、処理を他のスレッドに渡す目的で`Thread.Sleep(1)`を使用する理由はなくなった。

なお、これらのメソッドはWindows RTや、ユニバーサルWindowsプラットフォーム環境では使用できないようだ。

# 参考情報
Is Thread.Sleep(1) special?
http://stackoverflow.com/questions/16584358/is-thread-sleep1-special

Thread.Yield メソッド (System.Threading)
https://msdn.microsoft.com/ja-jp/library/system.threading.thread.yield%28v=vs.110%29.aspx

SwitchToThread function (Windows)
https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms686352%28v=vs.85%29.aspx

Thread.Sleep メソッド (Int32) (System.Threading)
https://msdn.microsoft.com/ja-jp/library/d00bd51t%28v=vs.110%29.aspx

Sleep function (Windows)
https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms686298%28v=vs.85%29.aspx

[^1]: 過去のMSDNをひもとくと、Windows XPとそれ以前のWindowsでの動作仕様だということがわかった。

[^2]: 同様に、Windows XPとそれ以前のWindowsでは、の意味。