「ダブル・チェック・ロッキング」の版間の差分

提供: MonoBook
ナビゲーションに移動 検索に移動
imported>GamerBook
 
(5人の利用者による、間の8版が非表示)
1行目: 1行目:
'''ダブル・チェック・ロッキング'''(英語:Double Check Locking)とは、初回は[[ロック]]なしで状態チェックを行い、そこで必要であれば再度[[ロック]]を掛けたのちに状態チェックを行うという[[ソフトウェア]]の[[最適化]]技法、[[デザインパターン]]のひとつである。
+
'''ダブル・チェック・ロッキング'''([[英語]]:double check locking)とは、[[マルチスレッド]]環境下における[[変数]]に対して、初回は[[ロック]]せず大雑把に状態チェックを行い、そこで必要であれば再度[[ロック]]を掛けた後に厳密に状態チェックを行うという[[ソフトウェア]]の[[最適化]]技法、[[デザインパターン]]のひとつである。
  
[[ロック]]は非常に[[オーバーヘッド]]の大きい重い処理であるため、その発生回数を可能な限り減らすことで高速化を実現しようというものである。
+
==概要==
 +
ダブル・チェック・ロッキングは、主に[[マルチスレッド]]環境下での[[シングルトンパターン]]を実装する際に[[オーバーヘッド]]の低減を目的として使われることが多い。
 +
 
 +
マルチスレッド下における[[変数]]の[[ロック]]は非常に[[オーバーヘッド]]が大きくスレッド数に比例して重くなる。ダブル・チェック・ロッキングはそのような状況下でロックの発生回数を可能な限り減らすことで[[プログラム]]の高速化を実現しようというものである。
  
ダブル・チェック・ロッキングは、主に[[マルチスレッド]]環境下での[[シングルトンパターン]]を実装する際に[[オーバーヘッド]]の低減を目的として使われることが多い。
+
なお、シビアな速度を要求されないのであれば[[ソースコード]][[可読性]]としては何も考えず常にロックした方が簡潔明瞭ではある。
  
 
== 主なプログラミング言語での実装例 ==
 
== 主なプログラミング言語での実装例 ==
23行目: 26行目:
 
     public static MySingleton GetInstance()
 
     public static MySingleton GetInstance()
 
     {
 
     {
         // 1回目のチェック
+
         // 1回目の大雑把なチェック
         // ロックしていないので高速に処理される
+
         // ロックしていない「nullではない場合」の処理が高速化されます。
 
         if (null == _instance)
 
         if (null == _instance)
 
         {
 
         {
 
             // ロック
 
             // ロック
             // ※このブロック内はクソ重い
+
             // ここで処理は急激に重くなります。
 
             lock (_sync)
 
             lock (_sync)
 
             {
 
             {
                 // 2回目のチェック
+
                 // 2回目の厳密なチェック
 
                 if (null == _instance)
 
                 if (null == _instance)
 
                 {
 
                 {
44行目: 47行目:
  
 
=== C# (Lazy) ===
 
=== C# (Lazy) ===
[[.NET Framework 4.0]]では、標準でLazy<T>クラスが用意されており、それを使うことでよりシンプルにダブル・チェック・ロッキングを記述できるようになった<!--<ref>{{cite  
+
[[.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
76行目: 79行目:
  
 
=== Java ===
 
=== Java ===
[[Java]]では仕様において[[アウトオブオーダー]]を用いるメモリモデルが可能となっていたため、この[[イディオム]]を使うことには問題があり、一部の実装では実際に正しく働かない可能性があることが知られている。詳細は http://www.ibm.com/developerworks/jp/java/library/j-dcl/ を参照。
+
[[Java]]では[[仕様]]において[[アウトオブオーダー]]を用いるメモリモデルが可能となっていたため、この[[イディオム]]を使うことには問題があり、一部の[[実装]]では実際に正しく働かない可能性があることが知られている。詳細は http://www.ibm.com/developerworks/jp/java/library/j-dcl/ を参照。大雑把にいえばJavaでは「new(メモリ確保)」と「コンストラクタ実行」のタイミングが異なる。
  
 +
よって、[[Java]]では絶対にダブルチェックロッキングを使用してはならない。
  
 
たとえば以下のような[[Java]]の[[ソースコード]]があったとする。
 
たとえば以下のような[[Java]]の[[ソースコード]]があったとする。
 
<source lang="java">
 
<source lang="java">
 
Person hage = new Person();
 
Person hage = new Person();
 +
hage.say();
 
</source>
 
</source>
 
これが一部のJava実装では以下のような動作になる。
 
これが一部のJava実装では以下のような動作になる。
# [[メモリ]]上に[[インスタンス]]が確保さる。
+
# 1行目で[[メモリ]]が確保される。
#: hage[[変数]]自体は[[null]]ではなくなる。
+
#: いわゆるmallocが実行されhage[[変数]]自体は[[null]]ではなくなる。
# [[コンストラクタ]]は実行されない。
+
#: ただし[[コンストラクタ]]はまだ実行されていない。
#: [[コンストラクタ]]が実行されるタイミングは、オリジナル(newを実行した[[スレッド]]の持つ)インスタンスに対して外部から[[メンバー関数]]や[[メンバー変数]]に初回アクセスがあったとき。これを[[遅延初期化]](lazy initialization)という。これによりhage[[変数]]自体は[[null]]ではないので後続スレッドはコンストラクタを実行していない不完全な[[インスタンス]]の[[参照]]を取得できてしまう。
+
# 2行目のインスタンスを初めて使うときにコンストラクタが実行される。
 +
#: [[コンストラクタ]]が実行されるタイミングは、オリジナル(newを実行した[[スレッド]]の持つ)インスタンスに対して外部から[[メンバー関数]]や[[メンバー変数]]に初回アクセスがあったときとなる。これを[[遅延初期化]](lazy initialization)という。これによりhage[[変数]]自体は[[null]]ではないので後続スレッドはコンストラクタを実行していない不完全な[[インスタンス]]の[[参照]]を取得できてしまう。
 +
 
 +
[[Objective-C]]のallocとinitの動作に近いものが全自動で実行されてしまうような感じである。
 +
<source lang="objc">
 +
Parson* hage = [[Parson alloc] init];
 +
</source>
  
 
<source lang="java">
 
<source lang="java">
120行目: 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では絶対にダブルチェックロッキングを使用してはならない。

たとえば以下のようなJavaソースコードがあったとする。

Person hage = new Person();
hage.say();

これが一部のJava実装では以下のような動作になる。

  1. 1行目でメモリが確保される。
    いわゆるmallocが実行されhage変数自体はnullではなくなる。
    ただしコンストラクタはまだ実行されていない。
  2. 2行目のインスタンスを初めて使うときにコンストラクタが実行される。
    コンストラクタが実行されるタイミングは、オリジナル(newを実行したスレッドの持つ)インスタンスに対して外部からメンバー関数メンバー変数に初回アクセスがあったときとなる。これを遅延初期化(lazy initialization)という。これによりhage変数自体はnullではないので後続スレッドはコンストラクタを実行していない不完全なインスタンス参照を取得できてしまう。

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;
}

関連項目[編集 | ソースを編集]

参考文献[編集 | ソースを編集]