13

完全にスレッドセーフなshared_ptr実装を知っている人はいますか? たとえば、ブーストの実装はshared_ptr、ターゲット (refcounting) に対してスレッドセーフであり、同時shared_ptrインスタンス読み取りに対しても安全ですが、書き込みまたは読み取り/書き込みに対しては安全ではありません。

( Boost docsの例 3、4、および 5 を参照してください)。

shared_ptrインスタンスに対して完全にスレッドセーフな shared_ptr 実装はありますか?

奇妙なブースト ドキュメントは次のように述べています。

shared_ptr オブジェクトは、組み込み型と同じレベルのスレッド セーフを提供します。

しかし、通常のポインター (組み込み型) を と比較すると、通常のポインターsmart_ptrの同時書き込みはスレッドセーフですが、 への同時書き込みはそうでsmart_ptrはありません。

編集: x86 アーキテクチャでのロックフリーの実装を意味します。

EDIT2: このようなスマート ポインターの使用例は、現在の作業項目でグローバル shared_ptr を更新する多数のワーカー スレッドと、作業項目のランダム サンプルを取得するモニター スレッドがある場合です。shared-ptr は、別の作業項目ポインターが割り当てられるまで (それによって前の作業項目を破棄する) 作業項目を所有します。モニターは、ワークアイテムを独自の shared-ptr に割り当てることにより、ワークアイテムの所有権を取得します (それにより、ワークアイテムが破棄されるのを防ぎます)。XCHG と手動削除で実行できますが、shared-ptr で実行できると便利です。

もう 1 つの例は、グローバル shared-ptr が「プロセッサ」を保持し、あるスレッドによって割り当てられ、他のスレッドによって使用される場合です。「ユーザー」スレッドは、プロセッサの shard-ptr が NULL であることを確認すると、代替ロジックを使用して処理を行います。NULL でない場合は、プロセッサを独自の shared-ptr に割り当てることで、プロセッサが破壊されるのを防ぎます。

4

9 に答える 9

18

このような完全にスレッドセーフな shared_ptr 実装に必要なバリアを追加すると、パフォーマンスに影響を与える可能性があります。次のレースを考えてみましょう (注: 疑似コードがたくさんあります)。

スレッド 1: global_ptr = A;

スレッド 2: global_ptr = B;

スレッド 3: local_ptr = global_ptr;

これを構成要素の操作に分解すると、次のようになります。

スレッド 1:

A.refcnt++;
tmp_ptr = exchange(global_ptr, A);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

スレッド 2:

B.refcnt++;
tmp_ptr = exchange(global_ptr, B);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

スレッド 3:

local_ptr = global_ptr;
local_ptr.refcnt++;

明らかに、スレッド 3 が A のスワップ後にポインターを読み取った場合、参照カウントがインクリメントされる前に B がそのポインターを削除すると、悪いことが起こります。

これを処理するには、スレッド 3 が refcnt の更新を行っている間にダミー値を使用する必要があります: (注: compare_exchange(variable, expected, new) は、変数の値が現在 new と等しい場合、原子的に new に置き換え、true を返します。成功した場合)

スレッド 1:

A.refcnt++;
tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

スレッド 2:

B.refcnt++;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

スレッド 3:

tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, BAD_PTR))
    tmp_ptr = global_ptr;
local_ptr = tmp_ptr;
local_ptr.refcnt++;
global_ptr = tmp_ptr;

/read/ 操作の途中でアトミックを含むループを追加する必要がありました。これは良いことではありません。一部の CPU では非常に高価になる可能性があります。その上、あなたも忙しく待っています。futex などで賢くなり始めることができますが、その時点でロックを再発明しています。

このコストは、すべての操作で負担する必要があり、とにかくロックによって得られるものと本質的に非常に似ているため、このようなスレッドセーフな shared_ptr 実装は一般的に見られません。そのようなことが必要な場合は、ミューテックスと shared_ptr を便利なクラスにラップして、ロックを自動化することをお勧めします。

于 2009-05-21T01:30:01.323 に答える
2

組み込みポインターへの同時書き込みは、確かにスレッドセーフではありません。本当に夢中になりたい場合は、メモリバリアに関して同じ値に書き込むことの意味を考慮してください(たとえば、同じポインターが異なる値を持つと考える2つのスレッドを持つことができます)。

RE: コメント - ビルトインが二重削除されない理由は、それらがまったく削除されないためです (そして、私が使用する boost::shared_ptr の実装は、特別なアトミック インクリメントとデクリメントを使用するため、二重削除しません。そのため、単一の削除のみになりますが、結果は一方からのポインターと他方の参照カウントを持つ可能性があります. または、2つのほとんどすべての組み合わせ. それは悪いでしょう.) ブーストドキュメントのステートメントはそのままで正しいです。組み込みの場合と同じ保証が得られます。

RE: EDIT2 - あなたが説明している最初の状況は、ビルトインと shared_ptrs の使用で大きく異なります。1 つ (XCHG および手動削除) には参照カウントがありません。これを行うときは、自分が唯一の所有者であると想定しています。共有ポインターを使用している場合は、他のスレッドが所有権を持っている可能性があるため、状況がはるかに複雑になります。コンペアアンドスワップで可能だと思いますが、これは非常に移植性がありません。

C++0x ではアトミック ライブラリが公開されており、汎用のマルチスレッド コードをより簡単に記述できるはずです。スレッド セーフなスマート ポインターの優れたクロスプラットフォーム リファレンス実装を確認するには、おそらくそれが出るまで待たなければならないでしょう。

于 2009-01-14T03:58:13.147 に答える
1

このようなスマート ポインターの実装については知りませんが、質問する必要があります。この動作はどのように役立つのでしょうか? 私が考えることができる唯一のシナリオは、ポインタの同時更新が発生する場所で、競合状態 (つまりバグ) です。

これは批判ではありません。正当な使用例があるかもしれませんが、私には思いつきません。私にお知らせください!

Re: EDIT2 いくつかのシナリオを提供していただきありがとうございます。そのような状況では、アトミック ポインターの書き込みが役立つように思えます。(ちょっとしたこと:2番目の例で、「NULLでない場合、プロセッサを独自の共有ptrに割り当てることでプロセッサが破壊されるのを防ぎます」と書いたとき、グローバル共有ポインタを最初ローカル共有ポインターが NULL かどうかを確認します。説明した方法では、グローバル共有ポインターをテストした後、ローカル共有ポインターに割り当てる前に、グローバル共有ポインターが NULL になるという競合状態が発生する傾向があります。)

于 2009-01-14T06:57:53.830 に答える
0

この実装のアトミック参照カウント ポインターを使用して、少なくとも参照カウント メカニズムを実装できます。

于 2010-05-04T19:41:06.227 に答える
-1

ご使用のコンパイラーは、新しいC++標準でスレッドセーフなスマートポインターをすでに提供している場合があります。TBBはスマートポインタの追加を計画していると思いますが、まだ含まれていないと思います。ただし、TBBのスレッドセーフコンテナの1つを使用できる場合があります。

于 2009-01-14T08:39:31.457 に答える
-1

これはそれほど簡単ではないと思います。sh_ptrクラスをCSでラップするだけでは不十分です。すべての共有ポインターに対して単一のCSを維持する場合、異なるスレッド間でのsh_ptrオブジェクトへの相互アクセスと削除を確実に回避できることは事実です。しかし、これはひどいことであり、共有ポインターごとに1つのCSオブジェクトが実際のボトルネックになります。ラップ可能なすべての新しいptr-sが異なるCS'を持っている場合は適切ですが、この方法でCSを動的に作成し、sh_ptrクラスのコピーctorがこの共有Csを送信するようにします。今、私たちは同じ問題に到達しました:このCsptrがすでに削除されているかどうかを誰が検疫しますか。インスタンスごとに揮発性のm_bReleasedフラグを使用すると、もう少し賢くなりますが、この方法では、フラグのチェックと共有Cの使用の間の安全性のギャップを埋めることができません。私は出来ます' この問題の完全に安全な解決策を確認してください。恐らく、そのひどいグローバルCは、アプリを殺すのと同じくらい悪いことでしょう。(私の英語でごめんなさい)

于 2009-09-11T13:48:34.307 に答える
-1

これは、すべての共有ポインターにミューテックス オブジェクトを含め、インクリメント/デクリメント コマンドをロックでラップすることで簡単に実行できます。

于 2009-05-21T01:45:46.487 に答える
-3

私の意見では、最も簡単な解決策はintrusive_ptr、いくつかのマイナーな(しかし必要な)変更を加えて使用することです。

私は以下の実装を共有しました:

http://www.philten.com/boost-smartptr-mt/

于 2011-01-17T09:24:40.457 に答える