4
void undefined_behaviour_with_double_checked_locking()
{
    if(!resource_ptr)                                    #1
    {
        std::lock_guard<std::mutex> lk(resource_mutex);  #2
        if(!resource_ptr)                                #3
        {
           resource_ptr.reset(new some_resource);        #4
        }
    }
    resource_ptr->do_something();                        #5
}

スレッドが別のスレッドによって書き込まれたポインターを認識した場合、新しく作成されたsome_resourceのインスタンスを認識できず、do_something()の呼び出しが誤った値で動作する可能性があります。これは、C ++標準によってデータ競合として定義され、未定義動作として指定された競合状態のタイプの例です。

質問>競合状態を引き起こすダブルチェックロックの問題がコードにある理由について、上記の説明を見ました。しかし、私はまだ問題が何であるかを理解するのに苦労しています。たぶん、具体的な2スレッドのステップバイステップのワークフローは、上記のコードの競合の問題を本当に理解するのに役立ちます。

この本で言及されている解決策の1つは次のとおりです。

std::shared_ptr<some_resource> resource_ptr;
std::once_flag resource_flag;

void init_resource()
{
    resource_ptr.reset(new some_resource);
}
void foo()
{
    std::call_once(resource_flag,init_resource); #1
    resource_ptr->do_something();
}
#1 This initialization is called exactly once

コメントは大歓迎です-ありがとう

4

2 に答える 2

8

この場合(との実装によっては.reset!、スレッド1が初期化の途中でresource_ptr一時停止/切り替えられるときに、問題が発生する可能性があります。次に、スレッド2が実行され、最初のチェックが実行され、ポインターがnullでないことを確認し、ロック/完全に初期化されたチェックをスキップします。次に、部分的に初期化されたオブジェクトを使用します(おそらく悪いことが起こります)。その後、スレッド1が戻って初期化を終了しますが、手遅れです。

部分的に初期化resource_ptrされる可能性がある理由は、CPUが(シングルスレッドの動作を変更しない限り)命令を並べ替えることができるためです。したがって、コードはオブジェクトを完全に初期化してからに割り当てる必要があるように見えますがresource_ptr、最適化されたアセンブリコードはまったく異なることをしている可能性があり、CPUはアセンブリ命令を指定された順序で実行することも保証されていませんバイナリ!

要点は、複数のスレッドが関係している場合、メモリフェンス(ロック)が正しい順序で発生することを保証する唯一の方法であるということです。

于 2011-12-20T05:00:14.063 に答える
5

最も単純な問題のシナリオは、の初期化some_resourceがに依存しない場合resource_ptrです。その場合、コンパイラは、をresource_ptr完全に構築する前に、自由に値を割り当てることができますsome_resource

たとえば、の操作をnew some_resource2つのステップで構成されていると考える場合:

  • にメモリを割り当てますsome_resource
  • 初期化some_resource(この説明では、この初期化では例外をスローできないという単純化した仮定を立てます)

次に、コンパイラがコードのミューテックスで保護されたセクションを次のように実装できることがわかります。

1. allocate memory for `some_resource`
2. store the pointer to the allocated memory in `resource_ptr`
3. initialize `some_resource`

ここで、別のスレッドがステップ2と3の間に関数を実行した場合、初期化されていないresource_ptr->do_something()ときに呼び出すことができることが明らかになりsome_resourceます。

一部のプロセッサアーキテクチャでは、適切なメモリバリアが設定されていない限り、この種の並べ替えがハードウェアで発生する可能性があることに注意してください(このようなバリアはミューテックスによって実装されます)。

于 2011-12-20T10:45:21.020 に答える