スレッドの落とし穴(4/6 ページ)

» 2005年03月23日 20時24分 公開
[石井宏治,ITmedia]

 .NETは、CPUではないが.NETの基礎となるMSILにもメモリモデルがある。ECMAでのMSILの定義は、IA64も視野に入れたWeak Memory Modelになっているのだ。MSILがより強いメモリモデルを採用してしまうと、IA64で十分なパフォーマンスを出せなくなってしまうからだ。

 前述の二重チェックロックのコードは、Strong Memory Modelであれば問題ないが、Weak Memory ModelのマルチプロセッサのPCでは問題になる。先に解答を示そう。解答は、利用する.NETのバージョンによって2種類ある。


using System.Threading;
public class A {
	private static A _Value;
	private static object ValueLock =
		new object();
	public static Value {
		get {
			if (_Value == null) {
				lock (ValueLock) {
					if (_Value == null) {
						A temp = new A();
						Thread.MemoryBarrier();
						_Value = temp;
					}
				}
			}
			Return _Value;
		}
	}
}

 ここで使ったThread.MemoryBarrierメソッドは、Visual Studio .NET 2003と共に出荷された.NET 1.1の新機能であるため、Visual Studio .NET 2002では使えない。.NET 1.0でも実行可能にする解答は、次のようになる。


public class A {
	private static volatile A _Value;
	private static object ValueLock =
		new object();
	public static Value {
		get {
			if (_Value == null) {
				lock (ValueLock) {
					if (_Value == null)
						_Value = new A();
				}
			}
			Return _Value;
		}
	}
}

 修正前のコードと似ているが、2行目に「volatile」キーワードを入れたことに注意してほしい。

 Weak Memory Modelにおいては、メモリへの書き込み順序が保障されない。このため元のコードでは、_Valueへ代入が行われた時点では_Valueオブジェクト自身への書き込みが終わっていない可能性がある。これを防ぐためには「volatile」キーワードを付加すればよいのだが、この方法を用いるとその変数へのすべてのアクセスにおいてCPU内部の最適化が禁止され、パフォーマンスの劣化を招く。

 .NET 1.1で追加されたMemoryBarrier()メソッドを使えば、問題の出る可能性のある場所に限って修正することができる。このため、パフォーマンスを落とさずにWeak Memory ModelのCPUでも動作するコードへと工夫ができる。

 さて、これで正しいコードとなったわけであるが、毎日書くようなコードではないだろう。そのためで、私自身でもこのコードを記憶しているわけではない。そして間違いがあってもほとんどの場合には動作してしまい、気づくのは稼働後のずっと後になってからになる。

 間違えた時の確認が非常に難しいため、ほんとうに必要な場合を除いてなるべく最初に示したコードを書くようにしている、というのを理解できただろうか。

Lock/SyncLockの代替え手段

 C#とVB.NETに組み込まれているlock/SyncLockは、非常に便利であるが、スレッド同期における問題は多岐に渡る。そのすべての用途に対する最適解とは言いがたい。

 スレッドを使う場合には、ほかの選択肢として何があるかを理解しておき、用途に合わせて正しいものを選択するようにしたい。ここではあまり知られていないが便利なクラスを2つ紹介しておこう。

Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ