std :: shared_ptr和std :: experimental :: atomic_shared_ptr有什么区别?

bob*_*eff 21 c++ concurrency smart-pointers atomic c++11

我阅读以下通过文章安东尼威廉姆斯和我除了理解为原子共享计数std::shared_ptrstd::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.这使得同步共享指针的工作成为手动的,我们不喜欢这样做.

这是原子共享指针发挥作用的地方.您可以从多个线程变异共享指针,而不必担心数据争用并且不使用任何锁定.独立功能将是成员功能,对用户来说,它们的使用将更加自然.这种指针对于无锁数据结构非常有用.

  • @bobeff libstdc ++和libc ++都使用互斥锁(!)的全局固定大小哈希表来在非原子`shared_ptr`之上实现这些操作.它们散列`shared_ptr`对象的地址以选择其中一个互斥锁,将其锁定,然后执行非原子操作. (3认同)
  • vc ++仅使用一个全局锁就更糟了 (3认同)
  • “原子”是围绕 CPU 提供的读写器内存栅栏的便利包装器。将类型声明为“原子的”只是使某种事物具有原子性的一种方式。 (2认同)

cpp*_*ner 6

N4162 (pdf),原子智能指针的提议,有一个很好的解释.以下是相关部分的引用:

一致性.据我所知,[util.smartptr.shared.atomic]函数是标准中唯一不能通过atomic类型获得的原子操作.对于所有类型 shared_ptr,我们教程序员在C++中使用原子类型,而不是 atomic_*C风格的函数.这部分是因为......

正确.使用自由函数会使代码在默认情况下容易出错.atomic在变量声明本身上写一次并且知道所有访问都是原子的,而不是必须记住在对象的每次使用atomic_*使用该操作,甚至是明显的读取,这要好得多.后一种风格容易出错; 例如,"做错了"意味着简单地写空白(例如,head而不是atomic_load(&head)),因此在这种风格中,变量的每次使用都是"默认错误的."如果你忘记atomic_*在一个地方写你的电话,你的代码仍然会成功编译而没有任何错误或警告,它将"似乎工作",包括可能通过大多数测试,但仍将包含一个具有未定义行为的静默竞赛,通常表现为间歇性的难以重现的故障,通常/通常在领域,我预计在某些情况下也会出现可利用的漏洞.通过简单地声明变量来消除这些类错误atomic,因为它默认是安全的并且编写相同的错误集需要显式的非空白代码(有时是显式 memory_order_*参数,通常是reinterpret_casting).

表现.atomic_shared_ptr<>作为一个独特的类型比[util.smartptr.shared.atomic]中的函数具有重要的效率优势 - 它可以atomic_flag像往常一样为内部自旋锁存储一个额外的(或类似的)atomic<bigstruct>.相比之下,现有的独立函数需要可以在任意shared_ptr 对象上使用,即使绝大多数shared_ptrs都不会以原子方式使用.这使得自由函数本身效率较低; 例如,实现可能要求每个人shared_ptr都携带内部自旋锁变量的开销(更好的并发性,但每个开销很大 shared_ptr),否则库必须维护一个旁视数据结构来存储shared_ptr实际上原子使用的s 的额外信息,或者(最差且在实践中显然很常见)库必须使用全局自旋锁.


atb*_*atb 5

呼叫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会在语法上冗余,可能会或可能不会导致性能损失.