1

DCLP (ダブルチェック ロック パターン) について読んでいますが、それが正しいかどうかわかりません。アトミックを使用してロックを作成する場合 ( C++11 で修正された DCLPで説明されているように)、不明な点が 2 つあります。

  1. 記事のコード:
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_acquire);
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            m_instance.store(tmp, std::memory_order_release);
        }
    }
    return tmp;
}

"load()" 内でフェンスを取得した後、tmp が nullptr ではなく、単純に戻るとどうなりますか? CPU が「フェンスを解放」できる場所を述べるべきではありませんか?

また、フェンスを解放する必要がない場合、なぜ取得と解放を行う必要があるのでしょうか。違いはなんですか?

Surly 私は何か基本的なものを欠いています....

  1. 記事を正しく入手した場合、それは DCLP を実装する正しい方法でもありますか?
Singleton* Singleton::m_instance = null;
std::atomic<bool> Singleton::is_first; // init to false
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    bool tmp = is_first.load(std::memory_order_acquire);
    if (tmp == false) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = is_first.load(std::memory_order_relaxed);
        if (tmp == false) {
            // can place any code that will run exactly once!
            m_instance = new Singleton;

            // store back the tmp atomically
            is_first.store(tmp, std::memory_order_release);
        }
    }
    return m_instance;
}

つまり、インスタンスを見る代わりに、アトミック ブール値を使用して DCLP が機能することを確認しています。それが正しいか?

ありがとう!

編集:シングルトンを実装するための質問をしているのではなく、単にフェンスとアトミックの概念とそれが DCLP をどのように修正したかをよりよく理解するために質問していることに注意してください。理論的な質問です。

4

1 に答える 1

4

"load()" 内でフェンスを取得した後、tmp が nullptr ではなく、単純に戻るとどうなりますか? CPU が「フェンスを解放」できる場所を述べるべきではありませんか?

いいえ。リリースは、ストアインが発生したときに行われm_instanceます。ロードm_instanceしてnullでない場合、リリースはすでに以前に行われており、それを行う必要はありません。

ミューテックス ロックを取得する場合のように、「フェンスを取得」して「フェンスを解放」することはありません。それはフェンスではありません。フェンスは、関連付けられたメモリ位置のない単なる取得または解放操作です。また、すべての取得操作と解放操作には関連するメモリ位置 (アトミック オブジェクトm_instance) があるため、フェンスはここではあまり関係ありません。

ミューテックスのロックとロック解除のように、ペアで取得と解放を行う必要はありません。値を格納するために 1 つのリリース操作を実行し、その値をロードしてその効果を観察する任意の数 (ゼロ以上) の取得操作を行うことができます。

ロード/ストアの取得/解放セマンティクスは、再順序付けを防ぐために、ロード/ストアの両側での操作の順序付けに関連しています。

変数 A への非緩和アトミック ストア (つまり解放操作) は、同じ変数 A のその後の非緩和アトミック ロード (つまり取得操作)と同期します。

C++ 標準が言うように:

非形式的には、A に対して解放操作を実行すると、他のメモリ位置に対する以前の副作用が、後で A に対して消費または取得操作を実行する他のスレッドに見えるようになります。

したがって、引用したDCLPコードでは、これm_instance.store(tmp, memory_order_release)は保存先m_instanceであり、解放操作です。からのm_instance.load(memory_order_acquire)ロードでm_instanceあり、取得操作です。メモリ モデルでは、null 以外のポインターのストアは、null 以外のポインターを参照するすべての読み込みと同期します。つまり、new Singletonスレッドが から null 以外の値を読み込む前に、 のすべての効果が完了することが保証されtmpます。tmpこれにより、オブジェクトが完全に構築される前にストア to が他のスレッドから見えるようになる可能性がある、C++11 以前のダブルチェック ロックの問題が修正されます。

つまり、インスタンスを見る代わりに、アトミック ブール値を使用して DCLP が機能することを確認しています。それが正しいか?

いいえ、ここに保存するためfalse:

        // store back the tmp atomically
        is_first.store(tmp, std::memory_order_release);

これは、関数の次の呼び出しで別の関数を作成しSingleton、最初の関数をリークすることを意味します。そのはず:

        is_first.store(true, std::memory_order_release);

それを修正すれば正しいと思いますが、典型的な実装ではより多くのメモリを使用し (sizeof(atomic<bool>)+sizeof(Singleton*)おそらく よりも多くsizeof(atomic<Singleton*>))、ロジックを 2 つの変数 (ブール値とポインター) に分割することで、間違いやすくなります。やりました。したがって、正しく設定されていない可能性のあるブール値ではなく、ポインターを直接見るため、ポインター自体もブール値として機能する元の方法と比較して、そのようにする利点はありません。

于 2015-06-02T10:13:46.530 に答える