我想知道是否可以为任何“常见”架构(如 x64 或 ARMv7 / ARMv8)创建无锁、线程安全的共享指针。
在cppcon2014 上关于无锁编程的演讲中,Herb Sutter 展示了无锁单链表的(部分)实现。该实现看起来很简单,但它依赖于shared_ptr
标准库中尚不存在的原子实现或使用专用std::atomic...
函数。这一点尤其重要,因为单个 push/pop 调用可能会调用多个原子加载/存储和compare_exchange
操作。
我看到的问题(我认为谈话中的一些问题朝着相同的方向发展)是,要使这成为一个实际的无锁数据结构,这些原子操作本身必须是无锁的。我不知道无std::atomic...
锁函数的任何标准库实现 - 至少通过简短的 google / SO 搜索 - 我也没有找到关于如何为std::atomic<std::shared_ptr>
.
现在在我浪费时间之前,我想问一下:
- 你知道,如果有可能写一个无锁的原子共享指针吗?
- 是否已经存在我忽略的任何实现,并且 - 理想情况下 - 甚至与您对 a 的期望兼容
std::atomic<std::shared_ptr>
?对于提到的队列,它特别需要CAS
- 操作。 - 如果没有办法在当前架构上实现这一点,那么与受锁保护的“普通”链表相比,您是否看到 Herb 的实现有任何其他好处?
作为参考,这里是 Herb Sutter 的代码(可能包含我的拼写错误):
template<class T>
class slist {
struct Node { T t; std::shared_ptr<Node> next; };
std::atomic<std::shared_ptr<Node>> head;
public:
class reference{
std::shared_ptr<Node> p;
public:
reference(std::shared_ptr<Node> p_){}
T& operator*(){ return p->t; }
T* operator->(){ return &p->t; }
};
auto find(T t) const {
auto p = head.load();
while (p && p-> != t) {
p = p - next;
}
return reference(move(p));
}
void push_front(T t) {
auto p = std::make_shared<Node>();
p->t = t;
p->next = head;
while (!head.compare_exchange_weak(p->next, p)) {}
}
void pop_front() {
auto p = head.load();
while (p && !head.compare_exchange_weak(p, p - next)) { ; }
}
};
请注意,在此实现中, a 的单个实例shared_ptr
可以由多个不同的线程访问/修改。它可以被读取/复制、重置甚至删除(作为节点的一部分)。所以这不是关于多个不同shared_ptr
的对象(管理同一个对象)是否可以在没有竞争条件的情况下被多个线程使用——这对于当前的实现已经是正确的并且是标准所要求的——而是关于对单个指针的并发访问例如,对于标准共享指针来说,它不会比对原始指针的相同操作更具线程安全性。
解释我的动机:
这主要是一个学术问题。我无意在生产代码中实现我自己的无锁列表,但我觉得这个话题很有趣,乍一看,Herb 的介绍似乎是一个很好的介绍。然而,在思考这个问题和@sehe 对我的回答的评论时,我想起了这个演讲,又看了一遍,意识到如果原始操作需要锁,那么将 Herb 的实现称为无锁并没有多大意义(他们目前所做的)。所以我想知道,这是否只是当前实现的限制还是设计中的根本缺陷。