交换两个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这样的惯用方法是什么?
无锁两个指针的交换
似乎有对这个问题没有一般无锁解决方案。 要做到这一点,你需要一个可能性,原子写入新的值到两个非连续的内存位置。 这就是所谓的DCAS
,但它不具备的英特尔处理器。
无锁所有权转移
这一个是可能的,因为它是只需要保存原子新值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;
}
见乔纳森的回答甚至更短的和优雅的解决方案。
无论如何,你将不得不编写自己的智能指针。 您不能使用这一招用标准unique_ptr
。
以原子修改两个变量的惯用方法是使用一个锁。
你不能做到这一点std::unique_ptr
没有锁。 甚至std::atomic<int>
不提供一种方式以原子交换两个值。 您可以更新一个原子,并得到其以前的值回来了,但交换是概念上的三个步骤,在方面std::atomic
API,他们是:
auto tmp = a.load();
tmp = b.exchange(tmp);
a.store(tmp);
这是一个原子读取随后的原子读-修改-写操作后紧跟一个原子写入 。 每一步都可以自动完成的,但你不能没有一个锁的时候,所有的三个原子。
对于不可复制的价值,如std::unique_ptr<T>
你甚至不能使用load
和store
操作上面,但必须做到:
auto tmp = a.exchange(nullptr);
tmp = b.exchange(tmp);
a.exchange(tmp);
这三年读-修改-写操作。 (你真的不能使用std::atomic<std::unique_ptr<T>>
要做到这一点,因为它需要一个平凡的,可复制的参数类型, std::unique_ptr<T>
是不是任何一种可复制。 )
为了用更少的操作做到这一点就需要所不支持的不同API std::atomic
,因为它不能被实现,因为作为斯塔斯的回答说,这是不可能的大多数处理器。 C ++标准是不规范的功能,所有现代的架构是不可能的习惯。 (不是故意反正!)
编辑:您更新的问题询问有关一个非常不同的问题,在第二个例子中,你不需要一个原子交换,影响两个对象。 只有global
的线程之间共享,所以你不关心,如果更新到local
是原子,你只需要更新原子global
和检索旧值。 规范C ++ 11做的方法是使用std:atomic<T*>
和你甚至不需要第二可变:
atomic<T*> global;
void f() {
delete global.exchange(new T(...));
}
这是一个读-修改-写操作。
这是对一个有效的解决方案
你必须写自己的智能指针
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();
并没有什么让t
活着的音频线时,GUI线程调用ptr->update(...)