Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 无锁堆栈-这是c+的正确用法吗+;11放松原子?可以证明吗?_C++_Multithreading_C++11_Atomic_Relaxed Atomics - Fatal编程技术网

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