4

Bクラスがインスタンス化され、複数のスレッドによって使用される、次の C++11 コードを検討してください。共有ベクトルを変更するためB、 の ctor およびメンバー関数 foo でアクセスをロックする必要がありBます。メンバー変数を初期化するidには、複数のスレッドからアクセスするため、アトミック変数であるカウンターを使用します。

struct A {
  A(size_t id, std::string const& sig) : id{id}, signature{sig} {}
private:
  size_t id;
  std::string signature;
};
namespace N {
  std::atomic<size_t> counter{0};
  typedef std::vector<A> As;
  std::vector<As> sharedResource;
  std::mutex barrier;

  struct B {
    B() : id(++counter) {
      std::lock_guard<std::mutex> lock(barrier);
      sharedResource.push_back(As{});
      sharedResource[id].push_back(A("B()", id));
    }
    void foo() {
      std::lock_guard<std::mutex> lock(barrier);
      sharedResource[id].push_back(A("foo()", id));
    }
  private:
    const size_t id;
  };
}

残念ながら、このコードには競合状態が含まれており、このようには機能しません (ctor と foo() が同じ ID を使用しない場合があります)。id の初期化をミューテックスによってロックされている ctor 本体に移動すると、次のように動作します。

struct B {
  B() {
    std::lock_guard<std::mutex> lock(barrier);
    id = ++counter; // counter does not have to be an atomic variable and id cannot be const anymore
    sharedResource.push_back(As{});
    sharedResource[id].push_back(A("B()", id));
  }
};

後者の例が機能する理由を理解するのを手伝ってもらえますか (同じミューテックスを使用していないためですか?)? ctorの本体でロックせずidに、イニシャライザリストで初期化する安全な方法はありますか? B私の要件は、そうでidなければならずconst、の初期化がid初期化子リストで行われることです。

4

3 に答える 3

2

まず、投稿されたコードにはまだ根本的なロジックの問題があります。として使用++ counteridます。B単一のスレッドでの の最初の作成を考えてみましょう。Bありid == 1ます。の後に、push_backsharedResourceありsharedResource.size() == 1、それにアクセスするための唯一の正当なインデックスは になります0

さらに、コードには明らかな競合状態があります。上記の問題を修正したとしても ( で初期化id) 、 と の両方が現在;であるとcounter ++仮定します。初期化したばかりです。スレッド 1 は、, incrementsのコンストラクタに入ります。countersharedResource.size()0Bcounter

counter == 1
sharedResource.size() == 0

その後、(mutex を取得する前に) スレッド 2 によって中断され、スレッド 2 もインクリメントcounterされ (2 に)、以前の値 (1) を として使用し idます。push_backただし、スレッド 2 の後には しかなくsharedResource.size() == 1、有効なインデックスは 0 だけです。

実際には、同じ値を持つ必要がある2 つの個別の変数 (counterと ) は避けます。sharedResource.size()経験から言うと、同じであるべき 2 つのことは同じではありません。冗長な情報を使用する必要があるのは、それが制御のために使用されるときだけです。つまり、ある時点でassert( id == sharedResource.size() )、または同様のものがあります。私は次のようなものを使用します:

B::B()
{
    std::lock_guard<std::mutex> lock( barrier );
    id = sharedResource.size();
    sharedResource.push_back( As() );
    //  ...
}

idまたは、 constを作成する場合:

struct B
{
    static int getNewId()
    {
        std::lock_guard<std::mutex> lock( barrier );
        int results = sharedResource.size();
        sharedResource.push_back( As() );
        return results;
    }

    B::B() : id( getNewId() )
    {
        std::lock_guard<std::mutex> lock( barrier );
        //  ...
    }
};

(これにはミューテックスを 2 回取得する必要があることに注意してください。または、更新 sharedResourceを完了するために必要な追加情報を に渡してgetNewId()、ジョブ全体を実行させることもできます。)

于 2012-04-05T07:50:48.570 に答える
1

オブジェクトを初期化するときは、単一​​のスレッドが所有する必要があります。そして初期化されたら共有にします。

スレッドセーフな初期化などがある場合、それはオブジェクトが初期化される前に他のスレッドからアクセス可能にならないようにすることを意味します。

もちろん、assignmentアトミック変数のスレッドセーフについて議論することはできます。割り当ては初期化とは異なります。

于 2012-04-05T01:20:30.547 に答える
0

ベクトルを初期化するサブコンストラクターリストにいます。これは実際にはアトミック操作ではありません。そのため、マルチスレッド システムでは、同時に 2 つのスレッドにヒットする可能性があります。これは、IDが何であるかを変更しています。スレッドセーフ 101 へようこそ!

初期化をロックで囲まれたコンストラクターに移動すると、1 つのスレッドのみがベクトルにアクセスして設定できるようになります。

これを修正するもう 1 つの方法は、これをシングルトン パターンに移動することです。ただし、オブジェクトを取得するたびにロックの料金を支払っています。

これで、ダブルチェックロックのようなものに入ることができます:)

http://en.wikipedia.org/wiki/Double-checked_locking

于 2012-04-05T01:45:23.033 に答える