0

オブジェクトの参照カウントがスレッドセーフであることを確認する方法について頭を悩ませようとしています。

class MyObject{
  //Other implementation details
private:
  mutable volatile LONGLONG * m_count;
  IData * m_data;
};

必要なクラス宣言がそこにあると仮定して、単純にしておきます。コピー コンストラクタとデストラクタの実装を次に示します。

MyObject::MyObject(const MyObject& rhs) : m_count(rhs.m_count), m_data(rhs.m_data){
    InterlockedIncrement64(m_count);
}

MyObject::~MyObject(){
    if(InterlockedDecrement64(m_count) == 0)
    delete m_data;
}

このスレッドは安全ですか? アトミックかどうかに関係なく、コピー コンストラクターの intilization リストはどのように表示されますか? それも問題ですか?初期化リストでカウントの増分値を設定する必要がありますか (これは可能ですか)?

このままで十分です。そうでなけれthread1thread2count == 1. スレッド間でハンドシェイクが必要です。つまり、スレッド 1 は、スレッド 2 のオブジェクトが範囲外になる前にオブジェクトを完全にコピーする必要があります。

これらの回答のいくつかを読んだ後、私は戻って少し調査を行いました. Boost は、非常によく似たように shared_ptr を実装しています。これがデストラクタの呼び出しです。

void release() // nothrow
{
    if( BOOST_INTERLOCKED_DECREMENT( &use_count_ ) == 0 )
    {
        dispose();
        weak_release();
    }
}

ブーストのドキュメントでは、割り当てはスレッドセーフではないと明確に述べていると示唆する人もいます。私は同意し、同意しません。私の場合、私は同意しないと思います。スレッド A とスレッド B の間のハンドシェイクのみが必要です。いくつかの返信で説明されている問題のいくつかは、ここでは当てはまらないと思います (ただし、それらは私が完全には考えていない目を見張るような返信でしたが)。

例 ThreadA atach(SharedObject); //値で渡された共有オブジェクト、カウントの増分など

ThreadB //オブジェクトを受け入れ、共有オブジェクトのリストに追加します。ThreadB は、すべての SharedObjects にイベントを通知するタイマー上にあります。通知する前に、クリティカル セクションによって保護されたリストのコピーが作成されます。CS がリリースされ、コピーが通知されます。

ThreadA detach(SharedObject); //オブジェクトのリストから共有オブジェクトを削除します

現在、同時に ThreadB は SharedOjbect をシグナリングしており、ThreadA が共有オブジェクトをデタッチする前にリストのコピーを作成しています。すべて大丈夫ですよね?

4

4 に答える 4

2

技術的には、安全でなければなりません。

なんで?オブジェクトをコピーするには、ソースに「参照」が必要なため、コピー中に参照が消えることはありません。また、現在作成中のオブジェクトには誰もアクセスしていません。

とにかく参照が残っていないので、デストラクタも安全です。

ただし、参照カウントのコピーを再検討することをお勧めします。これらの参照は実際には存在しません。コピーが作成される前に元の参照を取得していれば、元の参照を持つすべての人は、何らかの形でコピーの参照カウントも減らす必要があります。コピーは、refcount が 1 の新しいオブジェクトのように開始する必要があります。

EDIT:同様に、代入演算子(既存のオブジェクトへのコピーのようなもの)を実装している場合、宛先オブジェクトのrefcountはそのままにしておく必要があります。

于 2012-09-14T16:47:10.807 に答える
1

初期化リストはアトミックではないため、コンストラクターは安全ではありません(標準ではこれへの参照は見つかりませんでしたが、とにかく実装するのはほとんどありません)。

そのため、現在のスレッドによって現在コピーされているオブジェクトを別のスレッドが削除すると (初期化リストの実行とInterlockedIncrement()実行の間)、壊れた (既に削除されたm_data)m_datam_count. これにより、少なくともm_data.

InterlockedIncrementctor 呼び出しの後、初期化の前にスレッドの切り替えが発生する可能性があるため、初期化子リストに配置しても役に立ちませんm_count

外部ロック(ミューテックスまたはクリティカルセクション)なしでスレッドセーフにすることが可能かどうかはわかりません。少なくとも ctor でカウンターをチェックし、ゼロの場合は例外をスローするか、「無効な」オブジェクトを作成できますが、これは良い設計ではありません。お勧めしません。

于 2012-09-14T17:02:18.603 に答える
0

このコードは、呼び出し元のコードが、この関数の実行中に参照によって渡されたオブジェクトが破棄されないことを保証する限り、安全です。これは、参照を受け取る関数の場合も同じであり、これを行わないようにするには、非常に懸命に作業する必要があります。

アトミックデクリメントは1つのスレッドでゼロになることが保証されているため、デストラクタは安全です。もしそうなら、他のすべてのスレッドがすでにオブジェクトの使用を終了し、独自のデクリメント操作を呼び出している必要があります。

これは、インターロックされた操作がすべて完全な障壁を持っていることを前提としています。

count == 1のときに、thread1がコピーし、thread2が同時に破棄するシナリオに入るにはどうすればよいですか?スレッド間にハンドシェイクが必要です。つまり、thread2のオブジェクトがスコープから外れる前に、thread1はオブジェクトを完全にコピーする必要があります。

各スレッドがオブジェクトへの独自の参照を持っている限り、それはできません。thread1がオブジェクトへの独自の参照を持っている限り、thread1のコピー中にオブジェクトを破棄することはできません。Thread1は、thread2の参照がなくなる前にコピーする必要はありません。これは、オブジェクトへの参照がない限り、オブジェクトに触れることは決してないためです。

個々の参照はスレッドセーフが弱いため、異なるスレッドで同時にアクセスしないでください。2つのスレッドが同じオブジェクトにアクセスする場合は、それぞれに独自の参照が必要です。オブジェクトへの参照を他のコード(場合によっては別のスレッド内)に与えるときは、次の一連の操作に従います。

  1. あなた自身の参照を持っています。

  2. 独自の参照から他のコードの参照を作成します。

  3. 今、あなたはあなたの参照を破壊するか、他の参照を与えるかもしれません。

于 2012-09-14T18:52:53.070 に答える
-1

コピーコンストラクターは安全ではなく、安全にすることはできません。

ただし、new / deleteを使用せず、(スコープによって)自動的に作成および破棄されたオブジェクトでのみ機能する場合は、クラスを安全に使用できます。

于 2012-09-14T20:21:02.657 に答える