Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ruby-on-rails-3/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++ 使用std::atomic的无锁队列_C++_Multithreading_C++11_Queue_Stdatomic - Fatal编程技术网

C++ 使用std::atomic的无锁队列

C++ 使用std::atomic的无锁队列,c++,multithreading,c++11,queue,stdatomic,C++,Multithreading,C++11,Queue,Stdatomic,我希望使用std::atomic创建一个无锁队列 下面是我第一次尝试这么做可能不太好: 模板 类原子队列 { 公众: 使用值_type=T; 私人: 结构节点 { 值\类型m \值; node*m_next; 节点*m_prev; 节点(常数值\类型和值): m_值(值), m_next(nullptr), m_prev(nullptr){} }; 私人: 标准::原子m_头=空PTR; 标准::原子m_tail=nullptr; 公众: 无效推送(常量值\类型和值) { 自动新建节点=新建节点

我希望使用
std::atomic
创建一个无锁队列
下面是我第一次尝试这么做可能不太好:

模板
类原子队列
{
公众:
使用值_type=T;
私人:
结构节点
{
值\类型m \值;
node*m_next;
节点*m_prev;
节点(常数值\类型和值):
m_值(值),
m_next(nullptr),
m_prev(nullptr){}
};
私人:
标准::原子m_头=空PTR;
标准::原子m_tail=nullptr;
公众:
无效推送(常量值\类型和值)
{
自动新建节点=新建节点(值);
node*tmp=nullptr;
if(m_tail.compare_exchange_strong(tmp,新节点))
{
m_head.store(新的_节点,标准::内存_顺序_released);
返回;
}
节点*old_tail;
做{
老尾巴=m尾巴;
新建\u节点->m\u prev=旧\u尾;
}而(!m_tail.compare_exchange_strong(旧的_tail,新的_节点));
新建节点->上一步->下一步=新建节点;
}
void pop()
{
if(m_head.load(std::memory_order_released)==nullptr)
{
返回;
}
node*tmp=nullptr;
节点*头=m_头;
if(m_tail.compare_exchange_strong(head,tmp))
{
主存储器(tmp,std::存储器顺序松弛);
返回;
}
节点*旧头;
做{
旧头=旧头;
}而(m_head&!m_head.compare_exchange_strong(old_head,old_head->m_next));
如果(旧头)
{
删除旧头;
}
}
bool empty()
{
返回m_head.load(std::memory_order_released)=nullptr;
}
值\类型和前()
{
node*head=m_head.load(标准::内存顺序获取);
返回头->m_值;
}
};
这里需要注意的是,我将
m_prev
存储在
node
上,以便在成功
push
后,可以更新
m_tail
m_next
,而不通过
m_tail
实际执行此操作,以防它已经被另一个线程更改。因此,即使另一个线程已经开始推送一个新值,当前线程仍然会将它所看到的
m_tail
m_next
链接到新节点

现在,就我所知,有一些事情并不是真正的线程安全的,我真的想不出解决这些问题的好方法:

让我们假设队列中的
thread1
pop
s是唯一的项,然后进入以下if语句:

node*tmp=nullptr;
节点*头=m_头;
if(m_tail.compare_exchange_strong(head,tmp))
{
//现在线程2开始工作
主存储器(tmp,std::存储器顺序松弛);
返回;
}
让我们假设
thread2
在标记点启动,以
将新值推送到队列中,将执行以下语句:

node*tmp=nullptr;
if(m_tail.compare_exchange_strong(tmp,新节点))
{
m_head.store(新的_节点,标准::内存_顺序_released);
返回;
}
让我们假设它完成了它的
push
ing而没有
thread1
继续,只有
thread1
继续,然后
thread1
才会执行:

m_head.store(tmp,std::memory_order_released);
返回;
通过将
m_head
设置为
nullptr
,基本上可以撤消
thread2
push
。 据我所知,内存顺序在这种情况下帮不了我,所以我不确定我的选择是什么

另一个有问题的场景是,假设我们有两个读卡器线程
thread3
thread4
做同样的工作:

while(true)
{
如果(!q.empty())
{
int v=q.front();
q、 pop();
std::stringstream;

stream您的实现中存在几个问题,其中一些问题您已经正确识别

  • 两个
    m_-head.store
    操作之间的竞争在
    m_-tail上的CAS之后
  • 此循环可能会受到以下问题的影响:
  • pop
    中删除节点后,您将立即
    删除它,但此时另一个线程可能仍有对它的引用并访问它(例如,pop中的另一个线程),从而导致空闲后使用(这也称为内存回收问题)。
    解释:假设两个线程当前在
    pop
    中,并已将相同的值读入
    old\u head
    。第一个线程继续,在
    m\u head
    上执行CAS,并在下一步中立即删除
    old\u head
    。只有现在第二个线程继续尝试更新de>m_head
    ,使用
    old_head->m_next
    作为新值。这意味着thread two撤销指向刚删除节点的指针
  • 您的设计需要两个单独的函数调用来从队列中弹出一个项并获取其值
  • 设计无锁甚至无锁算法本身就很困难。问题2.和3.都可以通过使用内存回收方案来解决。问题4.通常通过不使用
    front
    操作来避免,而是让
    pop
    返回项目(直接通过
    std::optional
    ,或通过
    try\u pop
    版本,该版本通过引用获取一个out参数并返回一个bool,指示操作是否成功)

    不管是哪种方式,我都建议使用一种已建立的无锁算法,如。不幸的是,如果您决定实现该算法,您仍然必须解决内存回收问题

    我可以参考
      do {
            old_head = m_head;
      } while (m_head && !m_head.compare_exchange_strong(old_head, old_head->m_next));