bob*_*eff 21 c++ concurrency smart-pointers atomic c++11
我阅读以下通过文章安东尼威廉姆斯和我除了理解为原子共享计数std::shared_ptr
在std::experimental::atomic_shared_ptr
实际指针到共享对象也是原子?
但是,当我读到的引用计数的版本lock_free_stack
在安东尼的书中描述了关于C++并发似乎对我来说,同样aplies也是std::shared_ptr
,因为功能,如std::atomic_load
,std::atomic_compare_exchnage_weak
被应用到的实例std::shared_ptr
.
template <class T>
class lock_free_stack
{
public:
void push(const T& data)
{
const std::shared_ptr<node> new_node = std::make_shared<node>(data);
new_node->next = std::atomic_load(&head_);
while (!std::atomic_compare_exchange_weak(&head_, &new_node->next, new_node));
}
std::shared_ptr<T> pop()
{
std::shared_ptr<node> old_head = std::atomic_load(&head_);
while(old_head &&
!std::atomic_compare_exchange_weak(&head_, &old_head, old_head->next));
return old_head ? old_head->data : std::shared_ptr<T>();
}
private:
struct node
{
std::shared_ptr<T> data;
std::shared_ptr<node> next;
node(const T& data_) : data(std::make_shared<T>(data_)) {}
};
private:
std::shared_ptr<node> head_;
};
Run Code Online (Sandbox Code Playgroud)
这两种类型的智能指针之间的确切区别是什么,如果std::shared_ptr
实例中的指针不是原子的,为什么上述无锁堆栈实现可能?
Dav*_*aim 21
原子"事物" shared_ptr
不是共享指针本身,而是它指向的控制块.意思是只要你不shared_ptr
跨越多个线程变异,你就可以了.请注意,复制 a shared_ptr
只会改变控制块,而不是shared_ptr
自身.
std::shared_ptr<int> ptr = std::make_shared<int>(4);
for (auto i =0;i<10;i++){
std::thread([ptr]{ auto copy = ptr; }).detach(); //ok, only mutates the control block
}
Run Code Online (Sandbox Code Playgroud)
改变共享指针本身,例如为多个线程分配不同的值,就是数据竞争,例如:
std::shared_ptr<int> ptr = std::make_shared<int>(4);
std::thread threadA([&ptr]{
ptr = std::make_shared<int>(10);
});
std::thread threadB([&ptr]{
ptr = std::make_shared<int>(20);
});
Run Code Online (Sandbox Code Playgroud)
在这里,我们通过使控制块指向与多个线程不同的值来改变控制块(这是可以的)以及共享指针本身.这不行.
该问题的解决方案是shared_ptr
使用锁来包装,但是这种解决方案在某种争用下不是那么可扩展,并且在某种意义上,失去了标准共享指针的自动感觉.
另一种解决方案是使用您引用的标准函数,例如std::atomic_compare_exchange_weak
.这使得同步共享指针的工作成为手动的,我们不喜欢这样做.
这是原子共享指针发挥作用的地方.您可以从多个线程变异共享指针,而不必担心数据争用并且不使用任何锁定.独立功能将是成员功能,对用户来说,它们的使用将更加自然.这种指针对于无锁数据结构非常有用.
N4162 (pdf),原子智能指针的提议,有一个很好的解释.以下是相关部分的引用:
一致性.据我所知,[util.smartptr.shared.atomic]函数是标准中唯一不能通过
atomic
类型获得的原子操作.对于所有类型shared_ptr
,我们教程序员在C++中使用原子类型,而不是atomic_*
C风格的函数.这部分是因为......正确.使用自由函数会使代码在默认情况下容易出错.
atomic
在变量声明本身上写一次并且知道所有访问都是原子的,而不是必须记住在对象的每次使用atomic_*
时都使用该操作,甚至是明显的读取,这要好得多.后一种风格容易出错; 例如,"做错了"意味着简单地写空白(例如,head
而不是atomic_load(&head)
),因此在这种风格中,变量的每次使用都是"默认错误的."如果你忘记atomic_*
在一个地方写你的电话,你的代码仍然会成功编译而没有任何错误或警告,它将"似乎工作",包括可能通过大多数测试,但仍将包含一个具有未定义行为的静默竞赛,通常表现为间歇性的难以重现的故障,通常/通常在领域,我预计在某些情况下也会出现可利用的漏洞.通过简单地声明变量来消除这些类错误atomic
,因为它默认是安全的并且编写相同的错误集需要显式的非空白代码(有时是显式memory_order_*
参数,通常是reinterpret_cast
ing).表现.
atomic_shared_ptr<>
作为一个独特的类型比[util.smartptr.shared.atomic]中的函数具有重要的效率优势 - 它可以atomic_flag
像往常一样为内部自旋锁存储一个额外的(或类似的)atomic<bigstruct>
.相比之下,现有的独立函数需要可以在任意shared_ptr
对象上使用,即使绝大多数shared_ptr
s都不会以原子方式使用.这使得自由函数本身效率较低; 例如,实现可能要求每个人shared_ptr
都携带内部自旋锁变量的开销(更好的并发性,但每个开销很大shared_ptr
),否则库必须维护一个旁视数据结构来存储shared_ptr
实际上原子使用的s 的额外信息,或者(最差且在实践中显然很常见)库必须使用全局自旋锁.
呼叫std::atomic_load()
或呼叫std::atomic_compare_exchange_weak()
在shared_ptr
功能上等同于呼叫atomic_shared_ptr::load()
或atomic_shared_ptr::atomic_compare_exchange_weak()
.两者之间不应有任何性能差异.调用std::atomic_load()
或std::atomic_compare_exchange_weak()
使用a atomic_shared_ptr
会在语法上冗余,可能会或可能不会导致性能损失.