2つのunique_ptr
sを交換しても、スレッドセーフであるとは限りません。
std::unique_ptr<T> a, b;
std::swap(a, b); // not threadsafe
アトミックポインタースワップが必要であり、unique_ptr
の所有権処理が好きなので、両方を組み合わせる簡単な方法はありますか?
編集:これが不可能な場合は、代替手段を用意しています。私は少なくともこのようなことをしたいです:
threadshared_unique_ptr<T> global;
void f() {
threadlocal_unique_ptr<T> local(new T(...));
local.swap_content(global); // atomically for global
}
C++ 11でこれを行う慣用的な方法は何ですか?
この問題に対する一般的なロックフリーの解決策はないようです。これを行うには、新しい値を2つの非連続メモリ位置にアトミックに書き込む可能性が必要です。これは DCAS
と呼ばれますが、Intelプロセッサでは使用できません。
これは、新しい値をglobal
にアトミックに保存し、その古い値を受け取るだけでよいため、可能です。私の最初のアイデアは CAS
操作を使用することでした。アイデアを得るために次のコードを見てください:
std::atomic<T*> global;
void f() {
T* local = new T;
T* temp = nullptr;
do {
temp = global; // 1
} while(!std::atomic_compare_exchange_weak(&global, &temp, local)); // 2
delete temp;
}
手順
global
の現在のtemp
ポインタを覚えてくださいlocal
がglobal
と等しい場合(他のスレッドによって変更されていない場合)、global
をtemp
に保存します。これが当てはまらない場合は、再試行してください。実際には、変更前の古いCAS
値を使用して特別な処理を行わないため、global
は過剰です。したがって、アトミック交換操作を使用できます。
std::atomic<T*> global;
void f() {
T* local = new T;
T* temp = std::atomic_exchange(&global, local);
delete temp;
}
さらに短くてエレガントなソリューションについては、Jonathanの answer を参照してください。
とにかく、独自のスマートポインターを記述する必要があります。このトリックは、標準のunique_ptr
では使用できません。
2つの変数をアトミックに変更する慣用的な方法は、ロックを使用することです。
ロックなしではstd::unique_ptr
を実行できません。 std::atomic<int>
でも、2つの値をアトミックに交換する方法は提供されていません。 1つをアトミックに更新して以前の値を取り戻すことができますが、スワップは、std::atomic
APIに関して、概念的には3つのステップです。
auto tmp = a.load();
tmp = b.exchange(tmp);
a.store(tmp);
これはアトミックreadの後にアトミックread-modify-writeが続き、その後にアトミックwriteが続きます。各ステップはアトミックに実行できますが、ロックなしでは3つすべてをアトミックに実行できません。
std::unique_ptr<T>
などのコピー不可能な値の場合、上記のload
およびstore
操作を使用することもできませんが、次のようにする必要があります。
auto tmp = a.exchange(nullptr);
tmp = b.exchange(tmp);
a.exchange(tmp);
これは3つのread-modify-write操作です。 (これを行うためにstd::atomic<std::unique_ptr<T>>
を実際に使用することはできません。それは、簡単にコピーできる引数の型が必要であり、std::unique_ptr<T>
はいかなる種類のコピーもできないためです。)
少ない操作でそれを行うには、std::atomic
でサポートされていない別のAPIが必要になります。これは、Stasの回答にあるように、ほとんどのプロセッサーでは実現できないため、実装できないためです。 C++標準は、現代のすべてのアーキテクチャでは不可能な機能を標準化する習慣はありません。 (とにかく意図的ではありません!)
編集:更新された質問は、非常に異なる問題について尋ねています。2番目の例では、2つのオブジェクトに影響を与えるアトミックスワップは必要ありません。スレッド間で共有されるのはglobal
だけなので、local
への更新がアトミックであるかどうかは気にせず、global
をアトミックに更新して古い値を取得するだけで済みます。これを行う標準的なC++ 11の方法はstd:atomic<T*>
を使用することであり、2番目の変数も必要ありません。
atomic<T*> global;
void f() {
delete global.exchange(new T(...));
}
これは、単一のread-modify-write操作です。
これは有効な解決策ですか
独自のスマートポインタを記述する必要があります
_template<typename T>
struct SmartAtomicPtr
{
SmartAtomicPtr( T* newT )
{
update( newT );
}
~SmartAtomicPtr()
{
update(nullptr);
}
void update( T* newT, std::memory_order ord = memory_order_seq_cst )
{
delete atomicTptr.exchange( newT, ord );
}
std::shared_ptr<T> get(std::memory_order ord = memory_order_seq_cst)
{
keepAlive.reset( atomicTptr.load(ord) );
return keepAlive;
}
private:
std::atomic<T*> atomicTptr{nullptr};
std::shared_ptr<T> keepAlive;
};
_
最後の@Jonathan Wakelyのスニペットに基づいています。
このようなものが安全であることを願っています:
_/*audio thread*/ auto t = ptr->get() );
/*GUI thread*/ ptr->update( new T() );
/*audio thread*/ t->doSomething();
_
問題は、次のようなことができることです。
_/*audio thread*/ auto* t = ptr->get();
/*GUI thread*/ ptr->update( new T() );
/*audio thread*/ t->doSomething();
_
gUIスレッドがptr->update(...)
を呼び出したときに、オーディオスレッドでt
を維持するものはありません