交换两个unique_ptr
并不保证是线程安全的。
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中这样做的惯用方法是什么?
答案 0 :(得分:19)
似乎没有针对此问题的通用无锁解决方案。为此,您需要有可能将新值原子地写入两个非连续的内存位置。这称为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;
}
请参阅Jonathan的answer以获得更简短,更优雅的解决方案。
无论如何,你必须编写自己的智能指针。您不能将此技巧用于标准unique_ptr
。
答案 1 :(得分:19)
以原子方式修改两个变量的惯用方法是使用锁。
没有锁定,您无法为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>
不是任何可复制的。)
要用较少的操作来完成它需要std::atomic
不支持的不同API,因为它无法实现,因为正如Stas的回答所说,大多数处理器都无法实现。 C ++标准不习惯标准化所有现代架构都不可能实现的功能。 (无论如何都不是故意的!)
编辑:您更新的问题询问了一个非常不同的问题,在第二个示例中,您不需要影响两个对象的原子交换。线程之间只共享global
,因此您不关心local
的更新是否是原子的,您只需要以原子方式更新global
并检索旧值。规范的C ++ 11方法是使用std:atomic<T*>
,你甚至不需要第二个变量:
atomic<T*> global;
void f() {
delete global.exchange(new T(...));
}
这是一个读 - 修改 - 写操作。
答案 2 :(得分:0)
这是
的有效解决方案您必须编写自己的智能指针
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线程调用t
ptr->update(...)
保持活动状态