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

Windowsプログラマーでスレッドをいちども使ったことがない人はいないだろう。CPUのマルチコア化によって、ソースコードが上から順に実行という定説がなくなったのは久しい。このdev .NET特集では、知らぬと怖いテクニックを解説する。

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

 スレッドをいちども使ったことのないWindowsプログラマーは少ないだろう。理由はいくつか考えられる。

 まず、利用者の目が肥えており、時間がかかる作業を裏で行い、応答性向上を要求されるようになったこと。そしてCPUの高速化によってI/O部との処理速度差が広がり、スレッドを使うことで全体的な効率を上げられるようになったことだ。

 今後CPUのマルチコア化にともない、スレッドへの要求はさらに高まっていくと思われる。

 しかし、マルチスレッドや非同期モデルといったものに対する人間の対応能力は残念ながら低く、これらを使わないプログラムよりも数段高い技能が要求される。言語研究者の間では、スレッドや非同期モデルを言語に組み込むことで敷居を下げる試みも行われているが、今日現在広く利用できる言語にそのような機能は含まれていない。

 筆者は昨今いくつかのプロジェクトを見てきて、その影響範囲や危険性を深く利用しないままスレッドを使うことで、大きな問題を抱えてしまうケースを目にする。スレッドは、作って実行するのは非常に簡単だが、その裏にあるリスクを理解しないまま多用すると、プロジェクトの崩壊をも招きかねないという、諸刃の剣である。

 本記事ではよく利用されるようなシンプルなケースを用いて、その裏に潜むリスクを分析してみよう。実際のプログラミングでは、すべてがここで挙げるほどきちんと考える必要があるとは思っていない。しかし、危険なものを扱っているという心構えを持ち、より注意深いプログラミングを心がけるためにも、ぜひご一読いただきたい。

シングルインスタンスのロジック

 スレッド同期の例題として、最初にアクセスされた時にオブジェクトを生成し、以後同じものを返す、という、いわゆるシングルインスタンスのロジックを考えてみる。

 よく見るコードは次に挙げるようなものだ。


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

 このコードは、幾つかの問題を含んでいる。読み進む前に、自分であればどのようなコードを書くかを少し考えてみてほしい。

  • 解答編

 問題点の解説は長くなるので、最初に99%はこれで済む、という回答を示してしまおう。


public class A {
	private static A _Value = new A();
	public static Value {
		get {
			return _Value;
		}
	}
}

 Static (VB.NETではShared) 変数の初期化は、システムによって1回しか実行されないことが保証されているため、スレッド同期操作は不要になる。

 C++経験者の中にはこの手法を嫌がる人もいるが、C++と.NETの違いを知ると納得する場合が多い。

 C++においては、すべてのグローバル変数やクラスの静的変数がプログラムのロード時に初期化される。C++で同様なコードを書くと、このコードを使う、使わないにかかわらずロード時に初期化が実行されてしまう。このため、起動速度に影響する場合がある。

 一方、.NETにおいては、クラスの静的変数はそのクラスが参照された時点で初期化される。このクラスをリンクしていても、コード中でクラスが参照されるまで実行は行われない。

 シングルインスタンスはそのクラスの最初の参照で利用されることが多いため、このコードでも実はほとんど差がないことが多いのだ。

  • 解説編

 ほとんどは、と前述したが、やはりそれでは足りない場合もあり得る。特定のデバイスなどの物理リソースを確保してしまうクラスなど、型の参照時ではなく、明示的にメソッドが呼ばれた時に初期化したい場合がある。

       1|2|3|4|5|6 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ