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++;多线程无锁队列崩溃_C++_Multithreading_Atomic_Memory Fences - Fatal编程技术网

C++ C++;多线程无锁队列崩溃

C++ C++;多线程无锁队列崩溃,c++,multithreading,atomic,memory-fences,C++,Multithreading,Atomic,Memory Fences,我试图更好地理解为多线程编码时控制内存顺序的方法。我过去经常使用互斥体来序列化变量访问,但我试图尽可能避免使用互斥体来提高性能 我有一个指针队列,它可能由许多线程填充,并被许多线程使用。它在单线程上运行良好,但在多线程运行时崩溃。看起来消费者可能会得到重复的指针,这导致他们被释放两次。这有点难说,因为当我输入任何打印语句时,它运行良好,不会崩溃 首先,我使用一个预先分配的向量来保存指针。我保留了3个原子索引变量来跟踪向量中需要处理的元素。可能值得注意的是,我尝试使用一种_队列类型,其中元素本身是

我试图更好地理解为多线程编码时控制内存顺序的方法。我过去经常使用互斥体来序列化变量访问,但我试图尽可能避免使用互斥体来提高性能

我有一个指针队列,它可能由许多线程填充,并被许多线程使用。它在单线程上运行良好,但在多线程运行时崩溃。看起来消费者可能会得到重复的指针,这导致他们被释放两次。这有点难说,因为当我输入任何打印语句时,它运行良好,不会崩溃

首先,我使用一个预先分配的向量来保存指针。我保留了3个原子索引变量来跟踪向量中需要处理的元素。可能值得注意的是,我尝试使用一种_队列类型,其中元素本身是原子的,但这似乎没有帮助。以下是更简单的版本:

std::atomic<uint32_t> iread;
std::atomic<uint32_t> iwrite;
std::atomic<uint32_t> iend;
std::vector<JEvent*> _queue;

// Write to _queue (may be thread 1,2,3,...)
while(!_done){
    uint32_t idx = iwrite.load();
    uint32_t inext = (idx+1)%_queue.size();
    if( inext == iread.load() ) return kQUEUE_FULL;
    if( iwrite.compare_exchange_weak(idx, inext) ){
        _queue[idx] = jevent; // jevent is JEvent* passed into this method
        while( !_done ){
            if( iend.compare_exchange_weak(idx, inext) ) break;
        }
        break;
    }
}
我应该强调的是,我真的很有兴趣理解为什么这不起作用。实现其他一些预先制作的包可以让我克服这个问题,但不能帮助我避免以后再次犯同样类型的错误

更新 我认为Alexandr Konovalov的回答是正确的(见下面他回答中的评论)。如果有人看到此页面,“写入”部分的更正代码为:

std::atomic<uint32_t> iread;
std::atomic<uint32_t> iwrite;
std::atomic<uint32_t> iend;
std::vector<JEvent*> _queue;

// Write to _queue (may be thread 1,2,3,...)
while(!_done){
    uint32_t idx = iwrite.load();
    uint32_t inext = (idx+1)%_queue.size();
    if( inext == iread.load() ) return kQUEUE_FULL;
    if( iwrite.compare_exchange_weak(idx, inext) ){
        _queue[idx] = jevent; // jevent is JEvent* passed into this method
        uint32_t save_idx = idx;
        while( !_done ){
            if( iend.compare_exchange_weak(idx, inext) ) break;
            idx = save_idx;
        }
        break;
    }
}
std::原子iread;
std::原子iwrite;
std::原子iend;
std::vector_队列;
//写入_队列(可能是线程1,2,3,…)
而(!\u完成){
uint32_t idx=iwrite.load();
uint32_t inxt=(idx+1)%\u queue.size();
如果(inxt==iread.load())返回kQUEUE_FULL;
if(iwrite.compare\u exchange\u弱(idx,inxt)){
_队列[idx]=jevent;//jevent是传递到此方法的jevent*
uint32\u t save\u idx=idx;
而(!\u完成){
如果(iend.比较交换弱(idx,inext))中断;
idx=保存_idx;
}
打破
}
}

对我来说,当有2个编写器和1个读卡器时,可能会出现一个问题。假设第一个编写器刚好在

 _queue[0] = jevent;
第二个写入程序通过iend发出信号,表示其_队列[1]已准备好读取。然后,读卡器通过iend看到_队列[0]已经准备好被读取,所以我们有了数据竞争


我建议您尝试Relacy Race Detector,它非常适用于此类分析。

ABA问题?您确定您已经有足够的争用,值得拥有一个无锁队列吗?阅读其他人是如何解决多生产者多消费者队列(mpmc)问题的可能是值得的,因为这可能会让您了解您缺少的内容。从我对这个问题的回忆来看,它要求你的支票比你现在的支票更复杂。@UKMonkey是的。我可以使它与锁一起工作,并将其从~4MHz降低到~35kHz。@Slava。这是可能的,因为队列用作振铃器缓冲区,所以索引确实回滚到相同的值。不管我使用了多少线程(只要超过1个),崩溃似乎都会很快发生。考虑到一些数字(队列大小与线程数),这似乎有点不太可能,但值得检查。我会做一些测试。谢谢你的建议。writer部分中的内部while循环的目的是防止出现这种情况(尽管我可能遗漏了一些东西)。在您描述的场景中,第二个writer线程应该卡在内部while循环中,直到iend设置为“1”。直到第一个writer线程通过相同的内部while循环并将iend从“0”更改为“1”,才会发生这种情况。直到它写入到_queue中的插槽时才会发生这种情况。嗯,iend的第1次运行。第2个writer线程中的compare_exchange_弱()肯定会返回fail,但第2次运行成功(因此线程离开内部循环),因为在第一次运行之后,idx被更新为当前的iend。Alexandr:我不得不盯着你的评论和我的代码看了一会儿,最后才意识到我还没有完全理解compare_exchange_。我没有意识到,如果调用“失败”,那么它会更新预期值(在我的例子中是“idx”)。这正是我这么做的原因。对我来说,这是一个令人尴尬的错误,但我现在很高兴能理解它。谢谢
 _queue[0] = jevent;