C#でマルチスレッドのベストプラクティスって何かある？(What are the best practices with multithreading in C#?)

StackExchange/Code Reviewでの[質問"Exporting doc types using queues and multithreading"](https://codereview.stackexchange.com/q/55299)への[Eric Lippert氏による回答](https://codereview.stackexchange.com/a/55335)より訳出。回答内容は[オリジナル投稿"What are the best practices with multithreading in C#?"](http://codereview.stackexchange.com/posts/55299/revisions)に呼応するため、編集前のタイトルを採用。原文および訳文のライセンスは[引用元サイト規約](http://stackexchange.com/legal)の通り [CC-BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) に従う。

（補足：回答内容のトーンに合わせて口語調かつ意訳気味に訳出しました。誤訳指摘および訳出改善は歓迎します。）

---

<!-- Rather than reviewing your specific code -- which at first glance seems very, very buggy -- let me answer your question directly: -->
あんたのそのコード -- ひと目見ただけで酷いバグだらけ -- をレビューするよりも、質問に直接答えていこうか：

<!-- > What are the best practices with multithreading in C#? -->
> C#でマルチスレッドのベストプラクティスって何かある？

<!-- 1. Don't. Multithreading is a bad idea. Programs are hard enough to understand already with a *single* point of control flow in them; why on earth would you want to add a second, third, fourth... ? Do you enjoy tracking down incredibly difficult-to-reproduce, impossible-to-understand bugs? Do you have an encyclopaedic knowledge of the differences between weak and strong memory models? No?  Then don't write multithreaded programs!  Processes are a perfectly good unit of work; if you have work to do, spawn a process. -->
<!-- 2. Oh, so you want to do multithreaded programming anyways.  In that case **use the highest level of abstraction available**.  Programming against threads directly means writing programs about *workers* when you could be writing programs about *jobs*. Remember, workers are just means to an end; what you really want to get done are the jobs.  Use the **[Task Parallel Library][1]**: tasks are *jobs*, threads are *workers*. Let the TPL figure out for you how to make efficient use of workers. -->
<!-- 3. Wait, let's go back to point one again.  Ask yourself **do you really need concurrency?** With true concurrency we have two or more points of control in a program *actually active at the same time*. With simulated concurrency the two points of control take turns using the processor, so they are not actually concurrent. Most of the time we end up with simulated concurrency, because there are more threads than there are processors. This leads us naturally to the conclusion that perhaps actual concurrency is not what we need. Much of the time what people really want is *asynchrony*; concurrency is one way to achieve asynchrony, but it is not the only way.  **Use the new [async/await functionality in C# 5][2]** to represent asynchronous workflows. -->
<!-- 4. If you are confused by the previous point, and think wait a minute, there is no way to achieve asynchrony without multiple threads of control in a process, read Stephen Cleary's article **[There Is No Thread][3]**.  Don't try to write multithreaded programs until you understand it. -->
<!-- 5. At all costs **avoid shared memory**. Most threading bugs are caused by a failure to understand real-world shared memory semantics. If you must make threads, treat them as though they were processes: give them everything they need to do their work and let them work *without* modifying the memory associated with any other thread. Just like a process doesn't get to modify the memory of any other process. -->
<!-- 6. Oh, so you want to share memory across threads even though doing so is incredibly bug prone? Again, let me go back to **use the highest level tool available to you**. Those tools were written by experts. You're not an expert. Do you know how to make a lazily-computed value written to a field such that you are *absolutely guaranteed* that the lazy computation executes no more than once and the field is never observed to be in an inconsistent state no matter what is in any given processor cache?  I don't. You probably don't. Joe Duffy does, which is why you should use the [`Lazy<T>`][4] primitive he wrote rather than trying to write your own. -->
<!-- 7. Oh, so you want to write your own code that shares memory? Then **take the lock** for heaven's sake. I see so much buggy code where people get into their heads that locks are expensive and they write this convoluted godawful wrong code that evades taking a lock that would have cost them **twelve nanoseconds** to just take. Avoiding a lock to save on its cost is like complaining that the electricity takes too long to get to the light bulb when you flip the switch. Locks are a powerful tool for ensuring memory consistency; if you're going to program shared memory at the thread level, you avoid locks at your peril. -->
<!-- 8. If you use `Thread.Sleep` with an argument other than zero or one in any production code, you are possibly doing something wrong. Threads are expensive; you don't pay a worker to sleep, so don't pay a thread to sleep either. If you are using sleeps to solve a correctness issue by avoiding a timing problem -- as you appear to be in your code -- then you definitely have done something deeply wrong. **Multithreaded code needs to be correct irrespective of accidents of timing**. -->

1. そんなものは無い。マルチスレッドは悪い考えだ。プログラム中に *1つ* の制御フローがあるだけでも理解するのが十分難しいってのに；一体どうして2つ目、3つ目、4つ目を増やそうとするんだ？途方も無く再現性が低くて、超難解なバグを見つけだす作業を楽しみたい？あんたは弱いメモリモデルと強いメモリモデルの違いについて十分な知識があるか？ない？じゃあマルチスレッドプログラムを書くのなんて止めとけ！プロセスは申し分のない処理単位だ；そういうことをしたければ、プロセスを生成するんだね。

2. ああ、とにかくマルチスレッドプログラミングをやりたいんだな。その場合は **提供されるなかで最もハイレベルの抽象を使え**。スレッドを直接扱うプログラミングは、*ジョブ(job)* に関するプログラムを書くのではなく、*ワーカ(worker)* に関するプログラムを書くことを意味する。思い出してほしい、ワーカは結末に過ぎない；あんたが本当に処理したいのはジョブのはずだ。**[Task Parallel Library][1]** を使え：タスクが *ジョブ(job)* で、スレッドが *ワーカ(worker)* だ。TPLはワーカの効率的な使い方を教えてくれるだろう。

3. 待て、1つ目の指摘に立ち返ってみよう。**本当に並行性が必要なのか？**と自問してくれ。真の並行性では、プログラム中に *同時刻において実際にアクティブな* 2つ以上の制御ポイントを持つことになる。疑似的な並行性ではプロセッサにより2つの制御ポイントが交互に実行されるため、それらは実際の並行処理ではない。プロセッサ数よりも多数のスレッドが存在するため、ほとんどの場合は疑似的な並行性に終わる。このことは、我々に必要なのは実際の並行性ではなかったのではという結論を導く。大抵の場合、人々が真に求めるのは *非同期性(asynchrony)* だ；並行性は非同期性を達成する一つの手段だが、唯一のものではない。非同期性のワークフローを表現したいなら、**新しく[C# 5.0で導入されたasync/await機能][2]を使え**。

4. あんたが前の指摘に混乱して、しばらく考えても、プロセス内で複数の制御スレッドなしに非同期性を達成する手段はないと考えるなら、Stephen Clearの記事 **[スレッドは存在しない(There Is No Thread)][3]** を読め。記事を理解するまではマルチスレッドプログラムを書こうとするな。

5. 是が非でも **メモリの共有を避けろ**。大半のスレッド関連バグは、現実世界での共有メモリセマンティクスに対する誤った理解から生じる。スレッド生成が必須だというなら、あたかもプロセスのように扱うんだ：あるスレッドの処理で必要となる全てを与え、他のスレッドに関連するメモリは変更 *せずに* 処理を実行しろ。あるプロセスが他プロセスのメモリを変更できないのと同じことだ。

6. あー、信じられない程バグの温床になるとしても、あんたはスレッド間でメモリを共有したいんだな？繰り返しになるが、**利用可能な最高レベルのツールを使う** ことを立ち返ってほしい。それらのツールは専門家により書かれている。あんたは専門家じゃない。遅延評価される値をフィールドへ書き出すとき、遅延評価が1回以上実行されずに、かつ任意のプロセッサキャッシュ状態においても一貫性のない状態に置かれたフィールドを決して観測しないことを *絶対的に保証* する方法を、あんたは知っているのか？俺は知らない。おそらくあんたもだ。Joe Duffyは知っているし、それが自前コードじゃなくて彼が書いた[`Lazy<T>`][4]プリミティブを使うべき理由だ。

7. あぁ、それでもあんたはメモリを共有するコードを書きたいんだな？なら一生のお願いだから **ロックを取ってくれ**。俺はロックが高価だと信じ込む連中によるバグだらけのコードを見てきたし、奴らはたった **12ナノ秒** を要するロック獲得コストを回避するために、入り組んで悲惨に間違ったコードを書くんだ。コスト抑制を理由にロックを避けることは、スイッチを入れたとき電球まで電気が届くのが遅すぎると不平を漏らすようなものだ。ロックはメモリ一貫性を保証する強力なツールだ；スレッドレベルでメモリを共有するプログラムをするなら、危険を覚悟でロックを避けることだね。

8. プロダクションコードで`Thread.Sleep`を引数0や1以外で使っているとしたら、それは恐らく間違ったことをしている。スレッドは高価だ；ワーカを眠らせるべきでないし、スレッドも眠らせるべきでない。タイミング問題を回避して正しい動作のためにsleepしている -- まさにコードがやっているように -- としたら、あんたは確実に深刻な間違いを犯している。**タイミングに依らずマルチスレッドコードは正しく動作する必要がある。**

[1]: http://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx
[2]: http://msdn.microsoft.com/en-us/library/hh191443.aspx
[3]: http://blog.stephencleary.com/2013/11/there-is-no-thread.html
[4]: http://msdn.microsoft.com/en-us/library/dd642331(v=vs.110).aspx