注意この記事の最新版は「.NETのマルチスレッドプログラミング」で公開しています。†マルチスレッドプログラミングでの難所はスレッドの同期の問題でしょう。「あるスレッドで実行される処理が終わらなければ別のスレッドの処理を実行できない」というような場合にスレッドの同期が必要になります。その中でも同期が必要なケースとして重要でありながら、初心者には分かり難いのが、複数のスレッドが同じオブジェクトにアクセスする時に必要となる同期です。ここではその、複数のスレッドが共有オブジェクトにアクセスする時の同期の方法について、基本を説明します。はじめに、複数のスレッドが同じオブジェクトにアクセスする時になぜ同期が必要になるのか説明します。下のコードをご覧ください。}}上記のプログラムでは、MyClass.Incrementメソッド内の「(何らかの処理)」の部分が多くても10回しか実行されないように、countフィールドでカウントし、制御しています。複数のスレッドからこのIncrementメソッドを呼び出したときでも、「(何らかの処理)」の部分が11回以上実行されることは絶対にないでしょうか?実は、十分ありえます。10)」を処理した場合、countの値はまだ9のままのためそのスレッドでも条件にパスしてしまい、結果として両方のスレッドで「(何らかの処理)」が実行されてしまいます。Condition)」と呼ばれています。このような競合状態を回避するために、スレッドの同期が必要となります。そのためには、MyClass.Incrementメソッドを次のように書き換えます。}}MyClass.Incrementメソッドの先頭と末尾にMonitor.Enter(this);とMonitor.Exit(this);を挿入しただけですが、これでこの間のコードブロックには1つのスレッドしか入れなくなります。(このように複数のスレッドが同時に実行しないことが保証された領域のことを「クリティカルセクション」と呼びます。)Monitor.Enterメソッドは指定されたオブジェクトに対する排他ロックを取得し、Monitor.Exitメソッドは排他ロックを解放します。あるスレッドがMonitor.Enterにより、あるオブジェクト(ここではthis)をロックすると、他のスレッドはMonitor.Enterで同じオブジェクトのロックを取得することができず、ロックを取得しているスレッドがMonitor.Exitを呼び出してロックを解放するまでブロックされます。Monitor.EnterとMonitor.Exitで囲まれた部分は一つのスレッドでしか実行できなくなり、2つ目以降のスレッドがアクセスしようとすると待機させられます。Monitorはオブジェクト(参照型)をロックするために使われるため、Monitor.Enterメソッドに値型の変数を渡してはいけないということに注意してください。(詳しくはヘルプ「Monitor」をご覧ください。)MonitorMonitor.Enterメソッドは複数回呼び出すこともできますが、このときは同じ数だけMonitor.Exitで解放します。(補足:クリティカルセクションはできるだけ短くすべきです。上記のコードでも場合によってはメソッド全体をクリティカルセクションにする必要はないでしょう。)上記のMonitor.EnterとMonitor.Exitで囲むだけの方法では多少問題があります。いうのは、例えばMonitor.Enterが呼び出されてからMonitor.Exitが呼び出されるまでの間にエラーが発生した時、ロックが解除されないということになってしまいます。これを回避するためには、try...finally...のfinally句内にMonitor.Exitを書いておけばよいのですが、これと全く同じことがlock(VB.NETではSyncLock)ステートメントで出来てしまいます。よって通常はこちらを使うべきです。先のコードをlockステートメントを使って書き直すと次のようになります。}}静的メソッド内でクリティカルセクションを指定する時は、ロックするオブジェクトとして、Typeオブジェクトを使用するのが一般的です。次にその例を示します。}}メソッド全体をクリティカルセクションとするには、MethodImplAttributeとMethodImplOptions.Synchronizedを使用することもできます。先のMyClass.Incrementメソッド全体をクリティカルセクションにするには次のようにします。}}最後に、マルチスレッドプログラミングで「競合状態」とともに注意すべき問題である「デッドロック」について説明します。デッドロックとは、複数のスレッドがお互いにブロックし合い、身動きができなくなる状況のことをいいます。次にその典型的な例を示しましょう。Console.WriteLine("スレッド1終了");}上のコードを実行させると、まずメインスレッドでオブジェクト"o1"をロックし、スレッド"1"でオブジェクト"o2"をロックします。その後、スレッド"1"は"o1"のロックを要求しますが、すでにメインスレッドでロックされているため、メインスレッドでロックが解放されるまでブロックされます。片やメインスレッドでも"o2"のロックを要求しますが、"o2"はスレッド"1"ですでにロックされているため、やはり解放されるまでブロックされます。結局双方のスレッドがお互いをブロックし、全く身動きができなくなってしまいます。デッドロックを避けるためには、むやみに同期機構を使わない、入念に計画する、Monitor.TryEnterでタイムアウトするようにする、などの対策が考えられます。