Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/127.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++_Data Structures_Concurrency_Lock Free - Fatal编程技术网

C++ 带原子的无锁堆栈

C++ 带原子的无锁堆栈,c++,data-structures,concurrency,lock-free,C++,Data Structures,Concurrency,Lock Free,我需要构建一个无锁堆栈实现。我阅读并理解所列免锁推送操作的功能 现在,我必须构建一个类似的pop操作版本。这就是我到目前为止所做的,但我认为存在一些并发问题: template <class T> bool CASStack<T>::pop(T& ret) { node<T>* old_head = head.load(std::memory_order_relaxed); if(old_head == nullptr) { return f

我需要构建一个无锁堆栈实现。我阅读并理解所列免锁推送操作的功能

现在,我必须构建一个类似的pop操作版本。这就是我到目前为止所做的,但我认为存在一些并发问题:

template <class T>
bool CASStack<T>::pop(T& ret) {
node<T>* old_head = head.load(std::memory_order_relaxed);

if(old_head == nullptr) {
    return false;
}

// from here on we can assume that there is an element to pop
node<T>* new_head;

do {
    new_head = old_head->next;
} while(!head.compare_exchange_weak(old_head, new_head, std::memory_order_acquire, std::memory_order_relaxed));

ret = old_head->data;

return true;
}
模板
布尔·卡斯塔克:流行音乐(T&ret){
node*old\u head=head.load(std::memory\u order\u released);
如果(旧头==nullptr){
返回false;
}
//从这里开始,我们可以假设有一个元素要弹出
节点*新头;
做{
新头=旧头->下一步;
}而(!head.compare_exchange_弱(旧_头、新_头、std::memory_order_acquire、std::memory_order_relaxed));
ret=旧头->数据;
返回true;
}
我想如果我在交换后删除旧的头,我也会遇到麻烦,对吗

编辑:更新问题

你的
节点*new\u head=old\u head->next是一条红鲱鱼;您从不使用此变量

在我的评论中,我建议您需要将它放入
do{}而(!CAS)
循环中,我认为您正在执行
head.CAS(旧头,新头)
。这将有我所说的问题,如果CAS必须重试,则将可能过时的指针放入列表中

但实际上您正在执行
head.CAS(old\u head,old\u head->next)
,它每次通过循环都从更新的
old\u head
生成“所需”值。这实际上是正确的,但很难理解,因此我建议使用
do{}while()
如下:

// FIXME: this may suffer from ABA problems; see other answers.
node<T>* pop(std::atomic<node<T>*> &head)
{
    // We technically need acquire (or consume) loads of head because we dereference it.
    node<T>* old_head = head.load(std::memory_order_acquire);

    node<T>* new_head;
    do {
        if(old_head == nullptr) {
           // need to re-check because every retry reloads old_head
           // pop in another thread might have emptied the list
            return nullptr;
        }

        new_head = old_head->next;
        // if head still equals old_head this implies the same relation for new_head
    } while(!head.compare_exchange_weak(old_head, new_head,
                                        std::memory_order_acquire));
    // Note the ordering change: acquire for both success and failure

    return old_head;  // defer deletion until some later time
}
//修复程序:这可能会遇到ABA问题;请参阅其他答案。
节点*pop(标准::原子和头)
{
//从技术上讲,我们需要获取(或消耗)大量的头部,因为我们取消了对它的引用。
node*old\u head=head.load(std::memory\u order\u acquire);
节点*新头;
做{
如果(旧头==nullptr){
//需要重新检查,因为每次重试都会重新加载旧的\u头
//弹出另一个线程可能会清空列表
返回空ptr;
}
新头=旧头->下一步;
//如果head仍然等于old_head,则表示新_head的关系相同
}而(!头。比较交换弱(旧头,新头,
std::内存(顺序获取);
//注意订购变更:获取成功和失败的信息
返回旧的_head;//将删除推迟到以后某个时间
}
(为了处理可能的ABA问题,可能需要一个指针+序列号结构。这样做的方式仍然允许有效地加载指针,这可能是非常有难度的:。另请参阅关于这个问题的其他解决ABA问题的答案;我不久前写了这个答案,并不保证整个问题加起来就是一个可用的l。)(免费堆叠!)

允许在比较交换中执行
old\u head->next
吗?这仍然是原子的吗

CAS仍然是原子的。任何编译的
compare\u exchange\u-weak
本身都是原子的。不过,编译器在函数调用之前评估参数,因此读取
old\u head->next
不是CAS所做的原子事务的一部分。它已经被单独读取到临时事务中。(使用单独的变量显式执行此操作,如
do{},而
循环是常见的。)

如果
node::next
node
atomic
成员,您应该考虑您希望用于该加载的内存顺序。但是对于纯堆栈,它不必是原子的,因为链表节点在堆栈上时永远不会被修改,只有在使用右
next
指针按下之前。共享只读访问不是一场竞赛


作为纯堆栈使用还可以减少删除问题:线程不能“窥视”头部节点或遍历列表。它们只能在弹出节点后查看节点内部,
pop
算法确保它们拥有节点的独占所有权(并负责删除节点)

但是
pop()
本身需要从
head
节点加载。如果另一个线程与我们竞争,并将
head
的内存返回给操作系统,我们可能会出错。因此我们确实存在删除问题,就像我在评论中提到的那样

简单地重复使用内存,对于大多数C++实现来说,这并不是问题:尽管我们会为<代码> OLDYHEAD >下一个< /COD>读取垃圾值,但是CAS会失败(因为头/<代码>指针在旧的头对象被释放之前一定已经改变了)所以我们永远不会对我们加载的伪值做任何事情。但是,我们的原子负载仍然是C++ UB与非原子存储器竞争。但是编译器必须证明,这个竞争在允许发射任何非正常ASM之前确实发生,并且所有主流CPU在ASM中都没有任何问题。

但除非你能保证
free()
删除
只需将内存放在一个空闲列表上,即它们不会
将其映射到
head
的加载和
old\u head->next
的排序之间,上述推理无法确保调用方立即删除
pop
的返回值。这只意味着问题不大可能发生(通过简单的测试很难检测到)


内存排序 我们加载
head
,然后期望指针指向有用的值。(即
old\u head->next
)这正是
内存消耗
给我们的。但是它很难使用,而且很难优化,编译器只是将它增强为
获取
,这使得不可能测试使用
消耗
的代码,所以我们真的希望
获取
用于我们所有的
头部

(在当前编译器中使用
consume
相当于
acquire
。如果您确实需要执行无障碍的数据依赖关系排序
template <class T>
bool CASStack<T>::pop(T& ret) {
    node<T>* new_head;

    // get the current head
    node<T>* old_head = head.load(std::memory_order_relaxed);

    do {
        // it is a null pointer iff our stack is empty
        if(old_head == nullptr) {
            return false;
        }

        // otherwise, we can dereference it and access its next node
        new_head = old_head->next;
    } while(!head.compare_exchange_weak(old_head, new_head, std::memory_order_acquire, std::memory_order_relaxed));

    // finally write the popped value into ret
    ret = old_head->data;
    return true;
}
do {
   x = stack[temp = top];
} while (cswap(&top, temp, temp-1) != temp);
struct uuidx { int index; very_large_int sequence; };
extern (volatile, atomic, whatever) struct uuidx top;

...
struct uuidx temp, next;
do {
    x = stack[(temp = top).index];
    next = (struct uuidx){.index = temp.index - 1,
                 .sequence = temp.sequence+1};
} while (cswap(&top, temp, next) != temp)
void push(T value) {
    auto stp = stack_pointer.load(memory_order_acquire);
    stack[++stp] = value;
    stack_pointer.store(stp, memory_order_release);
}

T pop() {
    auto stp = stack_pointer.load(memory_order_acquire);
    while(true) {
        auto value = stack[stp];
        if (stack_pointer.atomic_compare_exchange_weak(stp,stp-1,memory_order_release,memory_order_acquire)) {
            return value;
        }
    }
}