繁体   English   中英

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

[英]What is the difference between std::shared_ptr and std::experimental::atomic_shared_ptr?

我阅读了Antony Williams以下文章,据我了解,除了std::experimental::atomic_shared_ptrstd::shared_ptr中的原子共享计数std::experimental::atomic_shared_ptr ,指向共享对象的实际指针也是原子的?

但是当我读到安东尼关于C++ 并发的书中描述的lock_free_stack引用计数版本时,在我看来,同样的情况也适用于std::shared_ptr ,因为像std::atomic_loadstd::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_;
};

这两种智能指针之间的确切区别是什么,如果std::shared_ptr实例中的指针不是原子的,为什么上面的无锁堆栈实现是可能的?

shared_ptr的原子“事物”不是共享指针本身,而是它指向的控制块。 这意味着只要您不跨多个线程改变shared_ptr ,就可以。 请注意,复制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 
}

改变共享指针本身,例如从多个线程为其分配不同的值,是一种数据竞争,例如:

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);
});    

在这里,我们通过使其指向来自多个线程的不同值来改变控制块(这没问题)以及共享指针本身。 这不行。

该问题的一个解决方案是将shared_ptr用锁包裹,但这种解决方案在某些争用下没有那么可扩展,并且从某种意义上说,失去了标准共享指针的自动感觉。

另一种解决方案是使用您引用的标准函数,例如std::atomic_compare_exchange_weak 这使得同步共享指针的工作成为我们不喜欢的手动工作。

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

N4162 (pdf) ,原子智能指针的提议,有很好的解释。 这是相关部分的引用:

一致性 据我所知,[util.smartptr.shared.atomic] 函数是标准中唯一不能通过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<bigstruct>简单地为内部自旋锁存储一个额外的atomic_flag (或类似的)。 相比之下,现有的独立函数需要可用于任何任意shared_ptr对象,即使绝大多数shared_ptr永远不会被原子使用。 这使得自由函数本质上效率较低; 例如,实现可能要求每个shared_ptr携带内部自旋锁变量的开销(更好的并发性,但每个shared_ptr开销很大),否则库必须维护一个后备数据结构来存储shared_ptr的额外信息,这些信息实际上是以原子方式使用,或者(在实践中最糟糕且显然很常见)库必须使用全局自旋锁。

shared_ptr上调用std::atomic_load()std::atomic_compare_exchange_weak()在功能上等同于调用atomic_shared_ptr::load()atomic_shared_ptr::atomic_compare_exchange_weak() 两者之间应该没有任何性能差异。 调用std::atomic_load()std::atomic_compare_exchange_weak()atomic_shared_ptr将语法多余的,可能会或可能不会导致性能下降。

atomic_shared_ptr是 API 的改进。 shared_ptr已经支持原子操作,但仅当使用适当的原子非成员函数时 这很容易出错,因为非原子操作仍然可用,并且对于粗心的程序员来说太容易意外调用。 atomic_shared_ptr不太容易出错,因为它不公开任何非原子操作。

shared_ptratomic_shared_ptr公开不同的 API,但它们不一定需要以不同的方式实现; shared_ptr已经支持atomic_shared_ptr公开的所有操作。 话虽如此, shared_ptr的原子操作并没有达到应有的效率,因为它还必须支持非原子操作。 因此, atomic_shared_ptr可以以不同的方式实现是有性能原因的。 这与单一职责原则有关。 “一个具有多个不同目的的实体......通常为其任何特定目的提供残缺的界面,因为各个功能领域之间的部分重叠模糊了清晰实现每个领域所需的愿景。” (Sutter & Alexandrescu 2005, C++ 编码标准

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM