質問

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

ブーストドキュメントの例を参照3、4、5)。

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

ブーストドキュメントが奇妙なことに言っている:

  

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

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

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

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

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

役に立ちましたか?

解決

このような完全にスレッドセーフな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では非常に高価になる可能性があります。さらに、あなたも忙しく待っています。フューテックスなどを使って賢くなり始めることができますが、その時点までにロックを再発明しました。

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

他のヒント

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

RE:コメント-組み込みが二重削除ではない理由は、それらがまったく削除していないためです(そして、私が使用するboost :: shared_ptrの実装は、特別なアトミック増分を使用するため、二重削除しませんそして、デクリメントするので、削除は1つだけになりますが、結果には、一方からのポインターと、もう一方の参照カウントがあります。または、この2つの組み合わせはほとんどありません。 boostドキュメントのステートメントはそのままで、組み込みの場合と同じ保証が得られます。

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

C ++ 0xには、一般的なマルチスレッドコードの記述をはるかに簡単にするアトミックライブラリが付属しています。スレッドセーフなスマートポインターのクロスプラットフォームリファレンスの実装を確認するには、おそらくそれが出るまで待つ必要があります。

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

これは批判ではありません。正当なユースケースが存在する可能性がありますが、考えられません。お知らせください!

Re:EDIT2 いくつかのシナリオを提供してくれてありがとう。このような状況では、アトミックポインター書き込みが役立つように聞こえます。 (1つの小さなこと:2番目の例では、" NULLでない場合、独自のshared-ptr"に割り当てることでプロセッサの破壊を防ぎます。グローバル共有ポインタを割り当てることを意味します。ローカル共有ポインタは、最初に then ローカル共有ポインタがNULLであるかどうかをチェックします-あなたがそれを記述した方法は、それをテストした後、割り当てる前にグローバル共有ポインタがNULLになる競合状態になりやすいですそれをローカルのものに)。

この実装原子参照カウントポインターを使用して、少なくとも実装することができます。参照カウント機構。

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

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

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

これはあなたが望むものとは限りませんが、 boost :: atomic のドキュメントには、 intrusive_ptr でアトミックカウンターを使用する方法の例が記載されています。 intrusive_ptr はBoostスマートポインターの1つであり、「侵入参照カウント」を実行します。つまり、カウンターが「埋め込み」されます。スマートポインターによって提供する代わりに、ターゲットで。

ブースト atomic 使用例:

http://www.boost.org/doc/html/atomic/ usage_examples.html

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

実装を以下で共有しました:

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top