Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/141.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++_Multithreading_Atomic_Race Condition - Fatal编程技术网

C++ 并发队列中的覆盖

C++ 并发队列中的覆盖,c++,multithreading,atomic,race-condition,C++,Multithreading,Atomic,Race Condition,我正在尝试编写一个无互斥(但不是无锁)队列,它使用一个连续的内存范围作为循环缓冲区和四个指针:两个用于使用者,两个用于生产者。它在最新推送的元素后保留一个空格,以消除满队列和空队列之间的歧义。以下是实现: template <typename T, typename Allocator = std::allocator<T>> class concurrent_queue { protected: T *storage; std::size_t s;

我正在尝试编写一个无互斥(但不是无锁)队列,它使用一个连续的内存范围作为循环缓冲区和四个指针:两个用于使用者,两个用于生产者。它在最新推送的元素后保留一个空格,以消除满队列和空队列之间的歧义。以下是实现:

template <typename T, typename Allocator = std::allocator<T>>
class concurrent_queue
{
protected:
    T *storage;
    std::size_t s;
    std::atomic<T*> consumer_head, producer_head;

    union alignas(16) dpointer
    {
        struct
        {
            T *ptr;
            std::size_t cnt;
        };
        __int128 val;
    };

    dpointer consumer_pending, producer_pending;

    Allocator alloc;

public:
    concurrent_queue(std::size_t s): storage(nullptr), consumer_head(nullptr), producer_head(nullptr)
    {
        storage = alloc.allocate(s+1);

        consumer_head = storage;
        __atomic_store_n(&(consumer_pending.val), (dpointer{storage, 0}).val, __ATOMIC_SEQ_CST);

        producer_head = storage;
        __atomic_store_n(&(producer_pending.val), (dpointer{storage, 0}).val, __ATOMIC_SEQ_CST);

        this->s = s + 1;
    }
    ~concurrent_queue()
    {
        while(consumer_head != producer_head)
        {
            alloc.destroy(consumer_head.load());
            ++consumer_head;
            if(consumer_head == storage + s)
                consumer_head = storage;
        }
        alloc.deallocate(storage, s);
    }

    template <typename U>
    bool push(U&& e)
    {
        while(true)
        {
            dpointer a;
            a.val = __atomic_load_n(&(producer_pending.val), __ATOMIC_RELAXED);
            std::atomic_thread_fence(std::memory_order_acquire);
            auto b = consumer_head.load(std::memory_order_relaxed);

            auto next = a.ptr + 1;
            if(next == storage + s) next = storage;

            if(next == b) continue;
            dpointer newval{next, a.cnt+1};
            if(!__atomic_compare_exchange_n(&(producer_pending.val), &(a.val), (newval.val), true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) continue;

            alloc.construct(a.ptr, std::forward<U>(e));

            while(!producer_head.compare_exchange_weak(a.ptr, next, std::memory_order_release, std::memory_order_relaxed));
            return true;
        }
    }

    template <typename U>
    bool pop(U& result)
    {
        while(true)
        {
            dpointer a;
            a.val = __atomic_load_n(&(consumer_pending.val), __ATOMIC_RELAXED);
            std::atomic_thread_fence(std::memory_order_acquire);
            auto b = producer_head.load(std::memory_order_relaxed);

            auto next = a.ptr + 1;
            if(next == storage + s) next = storage;

            if(a.ptr == b) continue;
            dpointer newval{next, a.cnt+1};
            if(!__atomic_compare_exchange_n(&(consumer_pending.val), &(a.val), (newval.val), true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) continue;

            result = std::move(*(a.ptr));
            alloc.destroy(a.ptr);

            while(!consumer_head.compare_exchange_weak(a.ptr, next, std::memory_order_release, std::memory_order_relaxed));
            return true;
        }
    }
};
模板
类并发队列
{
受保护的:
T*储存;
标准:尺寸;
标准::原子消费者负责人、生产者负责人;
union alignas(16)数据指针
{
结构
{
T*ptr;
标准:尺寸/碳纳米管;
};
__int128 val;
};
数据点消费者待定,生产者待定;
分配程序alloc;
公众:
并发队列(std::size\t s):存储(nullptr)、消费者(nullptr)、生产者(nullptr)
{
存储=分配分配(s+1);
消费者头=存储;
__原子存储(&(consumer_pending.val),(dpointer{storage,0}).val,u原子顺序CST);
生产者头=储存;
__原子存储(&(producer_pending.val),(dpointer{storage,0}).val,u原子顺序CST);
这->s=s+1;
}
~concurrent_queue()
{
while(消费者头!=生产者头)
{
alloc.destroy(用户头加载());
++消费者联合会主席;
if(用户头==存储+s)
消费者头=存储;
}
分配-解除分配(存储,s);
}
模板
布尔推送(U&e)
{
while(true)
{
D点a;
a、 val=uuu-atomic_-load_n(&(producer_-pending.val),uuu-atomic_-released);
std::原子线程围栏(std::内存顺序获取);
auto b=用户头加载(标准::内存顺序松弛);
自动下一步=a.ptr+1;
如果(next==存储+s)next=存储;
如果(next==b)继续;
dpointer newval{next,a.cnt+1};
如果(!\uuuu-atomic\u-compare\u-exchange\n(&(producer\u-pending.val),&(a.val),(newval.val),true,\uuu-atomic\u-ACQUIRE,\uuu-atomic\u-released))继续;
分配构造(a.ptr,std::forward(e));
而(!producer_head.compare_exchange_弱(a.ptr,next,std::memory_order_release,std::memory_order_release));
返回true;
}
}
模板
布尔波普(U和结果)
{
while(true)
{
D点a;
a、 val=uuu原子负载n(&(consumer_pending.val),uu原子负载n);
std::原子线程围栏(std::内存顺序获取);
自动b=生产商头加载(标准::内存顺序松弛);
自动下一步=a.ptr+1;
如果(next==存储+s)next=存储;
如果(a.ptr==b)继续;
dpointer newval{next,a.cnt+1};
如果(!(原子)比较交换(&(consumer_pending.val),&(a.val),(newval.val),true,原子获取,原子)松弛)继续;
结果=标准::移动(*(a.ptr));
允许破坏(a.ptr);
而(!consumer_head.compare_exchange_弱(a.ptr,next,std::memory_order_release,std::memory_order_release));
返回true;
}
}
};
但是,当使用相同数量的单独推送和弹出线程进行测试时,每个推送/弹出线程在终止之前具有相同数量的预定元素,一些弹出线程有时(并非总是)在执行过程中的某个点卡在第一个CA处,并且永远不会终止,即使在所有推送线程终止之后也是如此。由于它们试图弹出与推线程推送相同数量的元素,我怀疑推线程在某个点上发生了覆盖

这是我第一次尝试编写并发容器,所以我对此非常缺乏经验……我已经盯着它看了一段时间了,还没有弄清楚到底出了什么问题。有没有经验丰富的人能解决这个问题


还有,有没有其他平台特定的方法来获得双幅CAS?

编辑:大多数内容是这篇文章实际上是假的。见评论

        dpointer a;
        a.val = __atomic_load_n(&(producer_pending.val), __ATOMIC_RELAXED);
        std::atomic_thread_fence(std::memory_order_acquire);
        auto b = consumer_head.load(std::memory_order_relaxed);
你绝对确定这是你认为的吗?此代码段不将a.val排序在b之前

std::原子线程围栏(std::内存顺序获取);保证围栏之后的内存读取操作不会在围栏之前重新排序。但没有任何东西可以阻止围栏上方的内存操作流向底部。编译器完全可以自由地向上移动acquire围栏,只要它没有与其他围栏重新排序

更抽象的是:

a = load relaxed
memory fence acquire -- memory operations below this line may not float upwards
b = load relaxed
此编译器可以将其转换为:

memory fence acquire
b = load relaxed
a = load relaxed
但不是这个:

a = load relaxed
b = load relaxed
memory fence acquire

此外,您应该真正避免内存限制,并在操作本身上添加获取/释放。这通常会为非x86目标生成更好的代码。对于x86来说,这并不重要,因为即使是一个普通的
mov
也足以提供顺序一致性。旁注,这是为x86_64上的GCC编写的(因为这是我现在正在编写的系统)。我不确定我是否相信你。我经常读到,acquire fences防止在加载之前加载,而在加载之后加载和存储会被重新排序。在我看来,像你所说的那样重新排序会使收购篱笆变得毫无意义。你说得很对,简单的篱笆是没有意义的。通常,没有任何东西可以阻止编译器在main()之前移动围栏,除非在某个地方有发布操作。关于这个来源的另一种措辞:更容易理解。注意“防止读取获取的内存重新排序”部分。“读取获取”与“仅获取”的区别很重要。另请参见(强烈推荐)35:10-54:00查看标题“在便携式C++11中使用围栏”下的部分。如果围栏像你说的那样起作用,那么他的例子就不起作用了;
Ready
标志的加载可能是“sink”