C++ 无锁堆栈-这是c+的正确用法吗+;11放松原子?可以证明吗?
我已经为需要跨线程同步的一段非常简单的数据编写了一个容器。我想要最好的表现。我不想使用锁。 我想使用“放松”原子。一部分是为了那一点点额外的魅力,另一部分是为了真正理解它们 我已经在这方面做了很多工作,现在我的代码通过了所有测试。但这还不完全是“证据”,所以我想知道我是否遗漏了什么,或者是否有其他方法可以测试这一点 我的前提是:C++ 无锁堆栈-这是c+的正确用法吗+;11放松原子?可以证明吗?,c++,multithreading,c++11,atomic,relaxed-atomics,C++,Multithreading,C++11,Atomic,Relaxed Atomics,我已经为需要跨线程同步的一段非常简单的数据编写了一个容器。我想要最好的表现。我不想使用锁。 我想使用“放松”原子。一部分是为了那一点点额外的魅力,另一部分是为了真正理解它们 我已经在这方面做了很多工作,现在我的代码通过了所有测试。但这还不完全是“证据”,所以我想知道我是否遗漏了什么,或者是否有其他方法可以测试这一点 我的前提是: struct node { node *n_; #if PROCESSOR_BITS == 64 inline constexpr node() : n
struct node
{
node *n_;
#if PROCESSOR_BITS == 64
inline constexpr node() : n_{ nullptr } { }
inline constexpr node(node* n) : n_{ n } { }
inline void tag(const stack_tag_t t) { reinterpret_cast<stack_tag_t*>(this)[3] = t; }
inline stack_tag_t read_tag() { return reinterpret_cast<stack_tag_t*>(this)[3]; }
inline void clear_pointer() { tag(0); }
#elif PROCESSOR_BITS == 32
stack_tag_t t_;
inline constexpr node() : n_{ nullptr }, t_{ 0 } { }
inline constexpr node(node* n) : n_{ n }, t_{ 0 } { }
inline void tag(const stack_tag_t t) { t_ = t; }
inline stack_tag_t read_tag() { return t_; }
inline void clear_pointer() { }
#endif
inline void set(node* n, const stack_tag_t t) { n_ = n; tag(t); }
};
using std::memory_order_relaxed;
class stack
{
public:
constexpr stack() : head_{}{}
void push(node* n)
{
node next{n}, head{head_.load(memory_order_relaxed)};
do
{
n->n_ = head.n_;
next.tag(head.read_tag() + 1);
} while (!head_.compare_exchange_weak(head, next, memory_order_relaxed, memory_order_relaxed));
}
bool pop(node*& n)
{
node clean, next, head{head_.load(memory_order_relaxed)};
do
{
clean.set(head.n_, 0);
if (!clean.n_)
return false;
next.set(clean.n_->n_, head.read_tag() + 1);
} while (!head_.compare_exchange_weak(head, next, memory_order_relaxed, memory_order_relaxed));
n = clean.n_;
return true;
}
protected:
std::atomic<node> head_;
};
- 重要的是要正确地推送和弹出节点,并且堆栈永远不会失效李>
- 我认为内存中的操作顺序只在一个地方重要:
- 在比较和交换操作本身之间。这是有保证的,即使是放松的原子
- “ABA”问题通过向指针添加标识号来解决。在32位系统上,这需要双字比较交换,而在64位系统上,指针未使用的16位用id号填充
- 因此:堆栈将始终处于有效状态。(对吗?)
struct node
{
node *n_;
#if PROCESSOR_BITS == 64
inline constexpr node() : n_{ nullptr } { }
inline constexpr node(node* n) : n_{ n } { }
inline void tag(const stack_tag_t t) { reinterpret_cast<stack_tag_t*>(this)[3] = t; }
inline stack_tag_t read_tag() { return reinterpret_cast<stack_tag_t*>(this)[3]; }
inline void clear_pointer() { tag(0); }
#elif PROCESSOR_BITS == 32
stack_tag_t t_;
inline constexpr node() : n_{ nullptr }, t_{ 0 } { }
inline constexpr node(node* n) : n_{ n }, t_{ 0 } { }
inline void tag(const stack_tag_t t) { t_ = t; }
inline stack_tag_t read_tag() { return t_; }
inline void clear_pointer() { }
#endif
inline void set(node* n, const stack_tag_t t) { n_ = n; tag(t); }
};
using std::memory_order_relaxed;
class stack
{
public:
constexpr stack() : head_{}{}
void push(node* n)
{
node next{n}, head{head_.load(memory_order_relaxed)};
do
{
n->n_ = head.n_;
next.tag(head.read_tag() + 1);
} while (!head_.compare_exchange_weak(head, next, memory_order_relaxed, memory_order_relaxed));
}
bool pop(node*& n)
{
node clean, next, head{head_.load(memory_order_relaxed)};
do
{
clean.set(head.n_, 0);
if (!clean.n_)
return false;
next.set(clean.n_->n_, head.read_tag() + 1);
} while (!head_.compare_exchange_weak(head, next, memory_order_relaxed, memory_order_relaxed));
n = clean.n_;
return true;
}
protected:
std::atomic<node> head_;
};
struct节点
{
节点*n;
#如果处理器_位==64
内联constexpr节点():n_{nullptr}{
内联constexpr节点(node*n):n_{n}{
内联void标记(const stack_tag_t){reinterpret_cast(this)[3]=t;}
内联堆栈_tag_t read_tag(){return reinterpret_cast(this)[3];}
内联无效清除_指针(){tag(0);}
#elif处理器_位==32
堆叠标签;
内联constexpr node():n_{nullptr},t_{0}{
内联constexpr节点(node*n):n{n},t{0}{
内联void标记(const stack_tag_t t){t_=t;}
内联堆栈_标记_t读取_标记(){return t_;}
内联无效清除_指针(){}
#恩迪夫
内联空集(node*n,const stack_tag_t){n\un=n;tag(t);}
};
使用std::内存\u顺序\u松弛;
类堆栈
{
公众:
constexpr stack():head_{}{}
无效推送(节点*n)
{
节点next{n},head{head_u.load(memory_order_released)};
做
{
n->n_u=头;n_u;
next.tag(head.read_tag()+1);
}而(!head.compare_exchange_弱(head,next,memory_order_released,memory_order_released));
}
布尔波普(节点*&n)
{
node clean,next,head{head\uu.load(内存\u顺序\u松弛)};
做
{
清洁。设置(头号,0);
如果(!clean.n)
返回false;
next.set(clean.n_uu->n_u,head.read_tag()+1);
}而(!head.compare_exchange_弱(head,next,memory_order_released,memory_order_released));
n=清洁。n;
返回true;
}
受保护的:
std::原子头;
};
与其他问题相比,这个问题有什么不同?放松原子学。他们对这个问题有很大的影响
那么,你认为呢?我有什么遗漏吗?
推送
被破坏,因为在比较DSAP
失败后,您没有更新节点->\u next
。当下一次compareAndSwap
尝试成功时,另一个线程可能从堆栈顶部弹出了最初与node->setNext
一起存储的节点。结果,一些线程认为它已经从堆栈中弹出了一个节点,但是这个线程已经将它放回了堆栈中。应该是:
void push(Node* node) noexcept
{
Node* n = _head.next();
do {
node->setNext(n);
} while (!_head.compareAndSwap(n, node));
}
另外,由于next
和setNext
使用memory\u order\u released
,因此不能保证\u head\u.next()
此处返回的是最近推送的节点。可能会从堆栈顶部泄漏节点。同样的问题显然也存在于pop
中:\u head.next()
可能会返回以前存在但不再位于堆栈顶部的节点。如果返回的值为nullptr
,则当堆栈实际上不是空时,可能无法弹出
pop
如果两个线程试图同时从堆栈中弹出最后一个节点,则也可能有未定义的行为。它们都为\u头看到相同的值。下一步()
,一个线程成功完成pop。另一个线程进入while循环-因为观察到的节点指针不是nullptr
-但是compareAndSwap
循环很快将其更新为nullptr
,因为堆栈现在为空。在循环的下一次迭代中,取消该nullptr以获得其\u next
指针,并随之产生许多欢闹
pop
显然也患有ABA。两条线可以看到s