「ダブル・チェック・ロッキング」の版間の差分
imported>Administrator |
Administrator (トーク | 投稿記録) (→C#) |
||
(6人の利用者による、間の12版が非表示) | |||
1行目: | 1行目: | ||
− | '''ダブル・チェック・ロッキング''' | + | '''ダブル・チェック・ロッキング'''([[英語]]:double check locking)とは、[[マルチスレッド]]環境下における[[変数]]に対して、初回は[[ロック]]せず大雑把に状態チェックを行い、そこで必要であれば再度[[ロック]]を掛けた後に厳密に状態チェックを行うという[[ソフトウェア]]の[[最適化]]技法、[[デザインパターン]]のひとつである。 |
− | [[ | + | ==概要== |
+ | ダブル・チェック・ロッキングは、主に[[マルチスレッド]]環境下での[[シングルトンパターン]]を実装する際に[[オーバーヘッド]]の低減を目的として使われることが多い。 | ||
− | + | マルチスレッド下における[[変数]]の[[ロック]]は非常に[[オーバーヘッド]]が大きくスレッド数に比例して重くなる。ダブル・チェック・ロッキングはそのような状況下でロックの発生回数を可能な限り減らすことで[[プログラム]]の高速化を実現しようというものである。 | |
+ | |||
+ | なお、シビアな速度を要求されないのであれば[[ソースコード]]の[[可読性]]としては何も考えず常にロックした方が簡潔明瞭ではある。 | ||
== 主なプログラミング言語での実装例 == | == 主なプログラミング言語での実装例 == | ||
23行目: | 26行目: | ||
public static MySingleton GetInstance() | public static MySingleton GetInstance() | ||
{ | { | ||
− | // | + | // 1回目の大雑把なチェック |
− | // | + | // ロックしていない「nullではない場合」の処理が高速化されます。 |
if (null == _instance) | if (null == _instance) | ||
{ | { | ||
// ロック | // ロック | ||
− | // | + | // ここで処理は急激に重くなります。 |
lock (_sync) | lock (_sync) | ||
{ | { | ||
− | // | + | // 2回目の厳密なチェック |
if (null == _instance) | if (null == _instance) | ||
{ | { | ||
44行目: | 47行目: | ||
=== C# (Lazy) === | === C# (Lazy) === | ||
− | [[.NET Framework 4.0]] | + | [[.NET Framework 4.0]]から標準でLazy<T>クラス(System名前空間)が用意されており、これを使うことで簡潔明瞭にダブル・チェック・ロッキングを記述できるようになった<!--<ref>{{cite |
|title=C# 4.0 in a Nutshell | |title=C# 4.0 in a Nutshell | ||
|last=Albahari | |last=Albahari | ||
72行目: | 75行目: | ||
return _instance.Value; | return _instance.Value; | ||
} | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | === Java === | ||
+ | [[Java]]では[[仕様]]において[[アウトオブオーダー]]を用いるメモリモデルが可能となっていたため、この[[イディオム]]を使うことには問題があり、一部の[[実装]]では実際に正しく働かない可能性があることが知られている。詳細は http://www.ibm.com/developerworks/jp/java/library/j-dcl/ を参照。大雑把にいえばJavaでは「new(メモリ確保)」と「コンストラクタ実行」のタイミングが異なる。 | ||
+ | |||
+ | よって、[[Java]]では絶対にダブルチェックロッキングを使用してはならない。 | ||
+ | |||
+ | たとえば以下のような[[Java]]の[[ソースコード]]があったとする。 | ||
+ | <source lang="java"> | ||
+ | Person hage = new Person(); | ||
+ | hage.say(); | ||
+ | </source> | ||
+ | これが一部のJava実装では以下のような動作になる。 | ||
+ | # 1行目で[[メモリ]]が確保される。 | ||
+ | #: いわゆるmallocが実行されhage[[変数]]自体は[[null]]ではなくなる。 | ||
+ | #: ただし[[コンストラクタ]]はまだ実行されていない。 | ||
+ | # 2行目のインスタンスを初めて使うときにコンストラクタが実行される。 | ||
+ | #: [[コンストラクタ]]が実行されるタイミングは、オリジナル(newを実行した[[スレッド]]の持つ)インスタンスに対して外部から[[メンバー関数]]や[[メンバー変数]]に初回アクセスがあったときとなる。これを[[遅延初期化]](lazy initialization)という。これによりhage[[変数]]自体は[[null]]ではないので後続スレッドはコンストラクタを実行していない不完全な[[インスタンス]]の[[参照]]を取得できてしまう。 | ||
+ | |||
+ | [[Objective-C]]のallocとinitの動作に近いものが全自動で実行されてしまうような感じである。 | ||
+ | <source lang="objc"> | ||
+ | Parson* hage = [[Parson alloc] init]; | ||
+ | </source> | ||
+ | |||
+ | <source lang="java"> | ||
+ | public static Singleton getInstance() | ||
+ | { | ||
+ | // 1: 先行スレッドから見た場合はnull | ||
+ | // | ||
+ | // 3: 後続スレッドから見た場合は非null、 | ||
+ | // ただしコンストラクタが実行されていない不完全なものである場合がある。 | ||
+ | if (instance == null) | ||
+ | { | ||
+ | synchronized(Singleton.class) | ||
+ | { | ||
+ | if (instance == null) | ||
+ | { | ||
+ | // 2: | ||
+ | // 先行スレッドによりメモリが割り当てられてnullではなくなるが、 | ||
+ | // この時点でコンストラクタは実行されていない。 | ||
+ | instance = new Singleton(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | return instance; | ||
} | } | ||
</source> | </source> | ||
82行目: | 131行目: | ||
== 参考文献 == | == 参考文献 == | ||
{{reflist}} | {{reflist}} | ||
− | |||
− | |||
− | |||
{{stub}} | {{stub}} |
2023年5月19日 (金) 05:08時点における最新版
ダブル・チェック・ロッキング(英語:double check locking)とは、マルチスレッド環境下における変数に対して、初回はロックせず大雑把に状態チェックを行い、そこで必要であれば再度ロックを掛けた後に厳密に状態チェックを行うというソフトウェアの最適化技法、デザインパターンのひとつである。
概要[編集 | ソースを編集]
ダブル・チェック・ロッキングは、主にマルチスレッド環境下でのシングルトンパターンを実装する際にオーバーヘッドの低減を目的として使われることが多い。
マルチスレッド下における変数のロックは非常にオーバーヘッドが大きくスレッド数に比例して重くなる。ダブル・チェック・ロッキングはそのような状況下でロックの発生回数を可能な限り減らすことでプログラムの高速化を実現しようというものである。
なお、シビアな速度を要求されないのであればソースコードの可読性としては何も考えず常にロックした方が簡潔明瞭ではある。
主なプログラミング言語での実装例[編集 | ソースを編集]
C#[編集 | ソースを編集]
C#における標準的なダブル・チェック・ロッキングの実装方法を示す。この記述方法ではC#のキーワードのひとつであるvolatileを用いているのがミソである。なおvolatileキーワードと同等の機能を提供していない.NET Framework系のプログラミング言語も多く、それらでは別の実装方法を検討する必要がある。
public class MySingleton
{
private static object _sync = new object();
private static volatile MySingleton _instance = null;
// プライベートコンストラクター
// ※本クラスを除き、newキーワードによるインスタンス生成を出来なくする。
private MySingleton()
{
}
//
public static MySingleton GetInstance()
{
// 1回目の大雑把なチェック
// ロックしていない「nullではない場合」の処理が高速化されます。
if (null == _instance)
{
// ロック
// ここで処理は急激に重くなります。
lock (_sync)
{
// 2回目の厳密なチェック
if (null == _instance)
{
_instance = new MySingleton();
}
}
}
return _instance;
}
}
C# (Lazy)[編集 | ソースを編集]
.NET Framework 4.0から標準でLazy<T>クラス(System名前空間)が用意されており、これを使うことで簡潔明瞭にダブル・チェック・ロッキングを記述できるようになった。 この記述方法はC#に依存した機能も特に使われておらず、.NET Framework系の様々なプログラミング言語へも特に悩むことなく移植・実装可能であると思われる。
using System;
public class MySingleton
{
private static readonly Lazy<MySingleton> _instance
= new Lazy<MySingleton>(() => new MySingleton());
private MySingleton()
{
}
public static MySingleton GetInstance()
{
return _instance.Value;
}
}
Java[編集 | ソースを編集]
Javaでは仕様においてアウトオブオーダーを用いるメモリモデルが可能となっていたため、このイディオムを使うことには問題があり、一部の実装では実際に正しく働かない可能性があることが知られている。詳細は http://www.ibm.com/developerworks/jp/java/library/j-dcl/ を参照。大雑把にいえばJavaでは「new(メモリ確保)」と「コンストラクタ実行」のタイミングが異なる。
よって、Javaでは絶対にダブルチェックロッキングを使用してはならない。
Person hage = new Person();
hage.say();
これが一部のJava実装では以下のような動作になる。
- 1行目でメモリが確保される。
- 2行目のインスタンスを初めて使うときにコンストラクタが実行される。
Objective-Cのallocとinitの動作に近いものが全自動で実行されてしまうような感じである。
Parson* hage = [[Parson alloc] init];
public static Singleton getInstance()
{
// 1: 先行スレッドから見た場合はnull
//
// 3: 後続スレッドから見た場合は非null、
// ただしコンストラクタが実行されていない不完全なものである場合がある。
if (instance == null)
{
synchronized(Singleton.class)
{
if (instance == null)
{
// 2:
// 先行スレッドによりメモリが割り当てられてnullではなくなるが、
// この時点でコンストラクタは実行されていない。
instance = new Singleton();
}
}
}
return instance;
}