5

そのため、複数のスレッドが遅延して作成されたシングルトンを初期化しようとするのを防ぐために一般的に使用される C++ の二重チェック ロックが壊れていると主張する多くの記事を見てきました。通常の二重チェック ロック コードは次のようになります。

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!instance)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;
        }

        return *instance;
    }
};

問題は明らかにインスタンスを割り当てる行です-コンパイラは自由にオブジェクトを割り当ててからそれにポインタを割り当てるか、またはポインタを割り当てられる場所に設定してから割り当てます。後者のケースはイディオムを壊します -- 1 つのスレッドはメモリを割り当ててポインタを割り当てますが、スリープ状態になる前にシングルトンのコンストラクタを実行しません -- 次に、2 番目のスレッドはインスタンスが null ではないことを確認し、それを返そうとします、まだ構築されていませんが。

の代わりにスレッドローカルブール値を使用してチェックするという提案を見ましたinstance。このようなもの:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;
    static boost::thread_specific_ptr<int> _sync_check;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!_sync_check.get())
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            // Any non-null value would work, we're really just using it as a
            // thread specific bool.
            _sync_check = reinterpret_cast<int*>(1);
        }

        return *instance;
    }
};

このようにして、各スレッドはインスタンスが一度作成されたかどうかをチェックしますが、その後停止します。これにより、パフォーマンスが低下しますが、すべての呼び出しをロックするほど悪くはありません。しかし、ローカルな静的 bool を使用した場合はどうなるでしょうか?:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static bool sync_check = false;
        static singleton* instance;

        if(!sync_check)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            sync_check = true;
        }

        return *instance;
    }
};

なぜこれがうまくいかないのですか?sync_check が別のスレッドで割り当てられているときに 1 つのスレッドによって読み取られたとしても、ガベージ値はゼロ以外であり、したがって true になります。この Dr. Dobb の記事では、命令の並べ替えをめぐってコンパイラとの戦いに勝つことはできないため、ロックする必要があると主張しています。これは何らかの理由で機能しないはずだと思いますが、その理由はわかりません。Dr. Dobb の記事にあるように、シーケンス ポイントの要件が失われているとすれば、ロックの後のコードをロックの前に並べ替えることができない理由がわかりませんこれにより、C++ マルチスレッドが壊れた期間になります。

ローカル変数であるため、コンパイラが sync_check をロックの前に特別に並べ替えることが許可されていることがわかると思います(静的であっても、参照やポインターを返していません)-しかし、これはまだ解決できます代わりに静的メンバー (事実上グローバル) にすることによって。

それで、これは機能しますか、それとも機能しませんか?なんで?

4

3 に答える 3

5

sync_checkとインスタンスへの書き込みはCPUで順不同で行われる可能性があるため、修正しても何も修正されません。例として、インスタンスへの最初の2つの呼び出しが2つの異なるCPUでほぼ同時に発生することを想像してください。最初のスレッドはロックを取得し、ポインタを初期化し、sync_checkをtrueに設定しますが、プロセッサはメモリへの書き込みの順序を変更する場合があります。もう一方のCPUでは、2番目のスレッドがsync_checkをチェックして、それがtrueであることを確認できますが、インスタンスはまだメモリに書き込まれていない可能性があります。詳細については、Xbox360およびMicrosoftWindowsのロックレスプログラミングに関する考慮事項を参照してください。

その場合、言及したスレッド固有のsync_checkソリューションが機能するはずです(ポインターを0に初期化すると仮定します)。

于 2009-06-16T15:49:47.030 に答える
1

これについての素晴らしい読み物があります (.net/c# 指向ですが) ここ: http://msdn.microsoft.com/en-us/magazine/cc163715.aspx

要するに、この変数アクセスの読み取り/書き込みの順序を変更できないことを CPU に伝える必要があるということです (元の Pentium 以降、ロジックが影響を受けないと判断した場合、CPU は特定の命令を並べ替えることができます)。 )、キャッシュが一貫していることを確認する必要があること (忘れないでください。開発者は、すべてのメモリが 1 つのフラットなリソースであると思い込んでいますが、実際には、各 CPU コアにはキャッシュがあり、共有されていないものもあります (L1 )、一部は時々共有される可能性があります (L2)) -- 初期化によってメイン RAM に書き込まれる可能性がありますが、別のコアのキャッシュに初期化されていない値が含まれている可能性があります。並行性セマンティクスがない場合、CPU はキャッシュがダーティであることを認識しない可能性があります。

C++側はわかりませんが、.netでは、変数へのアクセスを保護するために、変数を揮発性として指定します(または、System.Threadingでメモリの読み取り/書き込みバリアメソッドを使用します)。

余談ですが、.net 2.0 では、ダブル チェック ロックが「揮発性」変数なしで動作することが保証されていることを読みました (.net リーダーの場合)。これは、C++ コードの役に立ちません。

安全を確保したい場合は、c# で変数を volatile としてマークするのと同等の c++ を実行する必要があります。

于 2009-06-03T15:05:43.170 に答える
0

「後者のケースはイディオムを壊します.2つのスレッドがシングルトンを作成することになるかもしれません.」

しかし、コードを正しく理解していれば、最初の例では、インスタンスが既に存在するかどうか (同時に複数のスレッドによって実行される可能性があります) を確認します。スレッドはその時点で作成を実行できます。他のすべてのスレッドはロックアウトされ、待機します。

インスタンスが作成されてミューテックスがロック解除されると、次の待機中のスレッドがミューテックスをロックしますが、チェックが失敗するため、新しいインスタンスを作成しようとしません。

次にインスタンス変数がチェックされると、それが設定されるため、スレッドは新しいインスタンスを作成しようとしません。

別のスレッドが同じ変数をチェックしている間に、あるスレッドが新しいインスタンス ポインターをインスタンスに割り当てている場合についてはわかりませんが、この場合は正しく処理されると思います。

ここで何か不足していますか?

操作の並べ替えについてはわかりませんが、この場合はロジックが変更されるため、それが起こるとは思いませんが、私はこのトピックの専門家ではありません。

于 2009-06-03T15:06:23.887 に答える