C++ 尝试实现无锁队列时堆栈溢出

C++ 尝试实现无锁队列时堆栈溢出,c++,multithreading,queue,lock-free,C++,Multithreading,Queue,Lock Free,我根据Maged M.Michael和Michael L.Scott工作中指定的算法实现了一个无锁队列(对于该算法,跳到第4页) 我在shared\u ptr上使用了原子操作,例如std::atomic\u load\u explicit等 当仅在一个线程中使用队列时,一切正常,但当从不同的线程中使用队列时,会出现堆栈溢出异常 不幸的是,我找不到问题的根源。似乎当一个shared_ptr超出范围时,它会减少下一个ConcurrentQueueNode上的引用数,并导致无限递归,但我不明白原因 守


我根据Maged M.Michael和Michael L.Scott工作中指定的算法实现了一个无锁队列(对于该算法,跳到第4页)

我在
shared\u ptr
上使用了原子操作,例如
std::atomic\u load\u explicit

当仅在一个线程中使用队列时,一切正常,但当从不同的线程中使用队列时,会出现堆栈溢出异常

不幸的是,我找不到问题的根源。似乎当一个
shared_ptr
超出范围时,它会减少下一个
ConcurrentQueueNode
上的引用数,并导致无限递归,但我不明白原因

守则:

队列节点:

template<class T>
struct ConcurrentQueueNode {
    T m_Data;
    std::shared_ptr<ConcurrentQueueNode> m_Next;

    template<class ... Args>
    ConcurrentQueueNode(Args&& ... args) :
        m_Data(std::forward<Args>(args)...) {}

    std::shared_ptr<ConcurrentQueueNode>& getNext() {
        return m_Next;
    }

    T getValue() {
        return std::move(m_Data);
    }

};
模板
结构ConcurrentQueueNode{
T m_数据;
std::shared_ptr m_Next;
模板
ConcurrentQueueNode(Args&&…Args):
m_数据(std::forward(args)…{}
std::shared_ptr&getNext(){
下一步返回m_;
}
T getValue(){
返回标准::移动(m_数据);
}
};
并发队列(注意:不适合胆小的人):

模板
类并发队列{
标准::共享的头、尾;
公众:
ConcurrentQueue(){
m_Head=m_Tail=std::使_共享();
}
模板
无效推送(Args&…Args){
自动节点=std::使_共享(std::转发(args)…);
std::共享_ptr尾;
对于(;;){
tail=std::原子加载\u显式(&m\u tail,std::内存\u顺序\u获取);
std::shared_ptr next=
std::atomic\u load\u explicit(&tail->getNext(),std::memory\u order\u acquire);
if(tail==std::atomic_load_explicit(&m_tail,std::memory_order_acquire)){
if(next.get()==nullptr){
auto currentNext=std::原子加载\显式(&m_Tail,std::内存\顺序\获取)->getNext();
auto res=std::atomic\u compare\u exchange\u弱(&tail->getNext(),&next,node);
如果(res){
打破
}
}
否则{
std::原子比较交换弱(&m_Tail,&Tail,next);
}
}
}
std::原子比较交换强(&m_Tail,&Tail,node);
}
布尔特里普(T&dest){
std::共享头;
对于(;;){
head=std::原子加载\u显式(&m\u head,std::内存\u顺序\u获取);
auto tail=std::原子加载\显式(&m_tail,std::内存\顺序\获取);
auto-next=std::原子加载\显式(&head->getNext(),std::内存\顺序\获取);
if(head==std::原子加载\u显式(&m\u head,std::内存\u顺序\u获取)){
if(head.get()==tail.get()){
if(next.get()==nullptr){
返回false;
}
std::原子比较交换弱(&m_Tail,&Tail,next);
}
否则{
dest=next->getValue();
auto res=std::原子比较交换弱(&m\U Head,&Head,next);
如果(res){
打破
}
}
}
}
返回true;
}
};
再现问题的示例用法:

int main(){
    ConcurrentQueue<int> queue;
    std::thread threads[4];

for (auto& thread : threads) {
    thread = std::thread([&queue] {

        for (auto i = 0; i < 100'000; i++) {
            queue.push(i);
            int y;
            queue.tryPop(y);
        }
    });
}

for (auto& thread : threads) {
    thread.join();
}
return 0;
}
intmain(){
并发队列;
标准:螺纹[4];
用于(自动线程:线程(&T){
thread=std::thread([&queue]{
用于(自动i=0;i<100'000;i++){
排队推送(i);
int-y;
tryPop(y);
}
});
}
用于(自动线程:线程(&T){
thread.join();
}
返回0;
}

问题在于竞态条件,它可能导致队列中的每个节点一次等待释放,这是递归的,会破坏堆栈

如果您将测试更改为仅使用一个线程但不弹出,则每次都会出现相同的堆栈溢出错误

for (auto i = 1; i < 100000; i++) {
  queue.push(i);
  //int y;
  //queue.tryPop(y);
}
for(自动i=1;i<100000;i++){
排队推送(i);
//int-y;
//tryPop(y);
}
您需要取消对节点链的递归删除:

__forceinline ~ConcurrentQueueNode() {
    if (!m_Next || m_Next.use_count() > 1)
        return;
    KillChainOfDeath();
}
void KillChainOfDeath() {
    auto pThis = this;
    std::shared_ptr<ConcurrentQueueNode> Next, Prev;
    while (1) {
        if (pThis->m_Next.use_count() > 1)
          break;
        Next.swap(pThis->m_Next); // unwire node
        Prev = NULL; // free previous node that we unwired in previous loop
        if (!(pThis = Next.get())) // move to next node
            break;
        Prev.swap(Next); // else Next.swap will free before unwire.
    }
}
\uu forceinline~ConcurrentQueueNode(){
如果(!m|u Next | m|u Next.use_count()>1)
返回;
KillChainOfDeath();
}
void KillChainOfDeath(){
自动pThis=这个;
std::共享\u ptr Next,Prev;
而(1){
如果(p此->m\u下一步。使用\u count()>1)
打破
Next.swap(pThis->m_Next);//解除节点
Prev=NULL;//释放我们在上一个循环中取消锁定的上一个节点
如果(!(pThis=Next.get())//移动到下一个节点
打破
Prev.swap(Next);//否则Next.swap将在解除连接之前释放。
}
}

我以前从未使用过shared_ptr,所以我不知道是否有更快的方法。另外,由于我以前从未使用过shared_ptr,我不知道您的算法是否会遇到ABA问题。除非共享的ptr实现中有什么特殊的东西来防止ABA,否则我担心以前释放的节点可能会被重用,从而欺骗CA。不过,当我运行您的代码时,我似乎从来没有遇到过这个问题。

我想我认为自己很幸运,我的并发需求总是由各种各样的互斥体来满足,它们具有定义良好的语义,而且我不需要愚弄自己在不稳定、复杂、“无锁”的备选方案上花费大量时间,这很奇怪,始终实现有效阻止执行的循环自旋锁,就像普通互斥锁一样。@SamVarshavchik我想你是对的,但如果你要实现无锁的东西,这总是一个很好的实验,我建议您的标准实现返回
true
以调用
std::atomic_是无锁的(&ptr)
,其中
ptr
是一些
std::shared_ptr
实例。只要在coliru上尝试了您的代码,就可以了。?@SamVarshavchik。你的观点有误导性。高性能多线程代码需要无锁。我们所有的实现都是在没有花费大量时间在不稳定的卷积代码上的情况下开发的。我们在har中节省的美元成本
__forceinline ~ConcurrentQueueNode() {
    if (!m_Next || m_Next.use_count() > 1)
        return;
    KillChainOfDeath();
}
void KillChainOfDeath() {
    auto pThis = this;
    std::shared_ptr<ConcurrentQueueNode> Next, Prev;
    while (1) {
        if (pThis->m_Next.use_count() > 1)
          break;
        Next.swap(pThis->m_Next); // unwire node
        Prev = NULL; // free previous node that we unwired in previous loop
        if (!(pThis = Next.get())) // move to next node
            break;
        Prev.swap(Next); // else Next.swap will free before unwire.
    }
}