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
Multithreading 无锁有界MPMC环缓冲区故障_Multithreading_C++11_Atomic_Lockless - Fatal编程技术网

Multithreading 无锁有界MPMC环缓冲区故障

Multithreading 无锁有界MPMC环缓冲区故障,multithreading,c++11,atomic,lockless,Multithreading,C++11,Atomic,Lockless,我一直在用头撞击(我的尝试)一个无锁的多生产者多消费者环形缓冲区。该思想的基础是使用无符号char和无符号short类型的固有溢出,将元素缓冲区修复为这两种类型中的任何一种,然后有一个返回到环形缓冲区开头的自由循环 问题是-我的解决方案不适用于多个生产者(虽然它适用于N个消费者,也适用于单个生产者和单个消费者) #包括,但我确实在尝试我的想法,然后与该方法进行比较,找出每种方法的优点(或者我的方法是否完全失败!) 我尝试过的事情 使用比较交换弱 使用更精确的std::memory\u顺序来匹配

我一直在用头撞击(我的尝试)一个无锁的多生产者多消费者环形缓冲区。该思想的基础是使用无符号char和无符号short类型的固有溢出,将元素缓冲区修复为这两种类型中的任何一种,然后有一个返回到环形缓冲区开头的自由循环

问题是-我的解决方案不适用于多个生产者(虽然它适用于N个消费者,也适用于单个生产者和单个消费者)

#包括,但我确实在尝试我的想法,然后与该方法进行比较,找出每种方法的优点(或者我的方法是否完全失败!)

我尝试过的事情

  • 使用比较交换弱
  • 使用更精确的std::memory\u顺序来匹配我想要的行为
  • 在我拥有的各种索引之间添加缓存线填充
  • 使元素std::原子化而不仅仅是元素数组
我确信这可以归结为我头脑中的一个根本错误,即如何使用原子访问来绕过互斥,我将非常感谢任何人能够指出哪些神经元在我头脑中严重失火!:)

我已经标出了有问题的地方。多个线程可以同时访问writeIndex=nextWriteIndex。数据将以任何顺序写入,尽管每次写入都是原子的

这是一个问题,因为您试图使用相同的原子条件更新两个值,这通常是不可能的。假设方法的其余部分没有问题,一种解决方法是将scratchIndex和writeIndex合并为一个大小为两倍的值。例如,将两个uint32_t值视为单个uint64_t值,并在其上进行原子操作。

这是。成功的制作人看起来是这样的:

  • 加载
    currentReadIndex
  • 加载
    currentWriteIndex
  • cmpxchg存储
    scratchIndex=nextWriteIndex
  • 存储
    元素
  • 存储
    writeIndex=nextWriteIndex
  • 如果某个生产者由于某种原因在步骤2和步骤3之间暂停足够长的时间,则其他生产者可能会生成整个队列的数据,并返回到完全相同的索引,以便步骤3中的比较交换成功(因为scratchIndex恰好再次等于currentWriteIndex)

    这本身不是问题。暂停的生产者完全有权增加
    scratchIndex
    以锁定队列,即使神奇的ABA检测到cmpxchg拒绝存储,生产者只需重试,重新加载完全相同的
    currentWriteIndex
    ,然后正常进行

    实际问题是步骤2和步骤3之间的
    nextWriteIndex==currentReadIndex
    检查。如果
    currentReadIndex==currentWriteIndex
    ,则队列在逻辑上是空的,因此此检查的存在是为了确保没有生产者领先太远,以至于它覆盖了还没有消费者弹出的元素。在顶部执行一次此检查似乎是安全的,因为所有使用者都应该被“困”在观察到的
    currentReadIndex
    和观察到的
    currentWriteIndex
    之间

    除了另一个制作人可以出现并启动
    writeIndex
    ,从而将消费者从陷阱中解放出来。如果生产者在第2步和第3步之间暂停,当它唤醒时,
    readIndex
    的存储值可能是任何值

    下面是一个示例,从一个空队列开始,它显示了发生的问题:

  • 生产者A运行步骤1和2。两个加载的索引都是0。队列是空的
  • 生产者B中断并产生一个元素
  • 消费者弹出一个元素。两项指数均为1
  • 生产者B再产生255个元素。写索引变为0,读索引仍然为1
  • 制作人A从沉睡中醒来。它以前将读和写索引都加载为0(空队列!),因此它尝试执行步骤3。因为另一个生产者碰巧在索引0上暂停,所以比较交换成功,存储继续进行。完成时,生产者让writeIndex=1,现在两个存储的索引都是1,队列逻辑上是空的。一个完整队列的元素值现在将被完全忽略
  • (我应该提到的是,我可以不谈论“暂停”和“醒来”的唯一原因是使用的所有原子都是顺序一致的,因此我可以假装我们处于单线程环境中。)



    请注意,您使用
    scratchIndex
    保护并发写入的方式本质上是一种锁;成功完成cmpxchg的人将获得对队列的总写访问权,直到队列释放锁为止。修复此故障的最简单方法是用自旋锁替换
    scratchIndex
    ,它不会受到a-B-a的影响,这就是实际发生的情况。

    那么,问题到底是什么?你想知道为什么这不起作用,或者如何使它起作用,或者…?我不知道为什么它不适用于多个生产者的情况!
    #include <atomic>
    
    template<typename Element, typename Index = unsigned char> struct RingBuffer
    {
      std::atomic<Index> readIndex;
      std::atomic<Index> writeIndex;
      std::atomic<Index> scratchIndex;
      Element elements[1 << (sizeof(Index) * 8)];
    
      RingBuffer() :
        readIndex(0),
        writeIndex(0),
        scratchIndex(0)
      {
        ;
      }
    
      bool push(const Element & element)
      {
        while(true)
        {
          const Index currentReadIndex = readIndex.load();
          Index currentWriteIndex = writeIndex.load();
          const Index nextWriteIndex = currentWriteIndex + 1;
          if(nextWriteIndex == currentReadIndex)
          {
            return false;
          }
    
          if(scratchIndex.compare_exchange_strong(
            currentWriteIndex, nextWriteIndex))
          {
            elements[currentWriteIndex] = element;
            writeIndex = nextWriteIndex;
            return true;
          }
        }
      }
    
      bool pop(Element & element)
      {
        Index currentReadIndex = readIndex.load();
    
        while(true)
        {
          const Index currentWriteIndex = writeIndex.load();
          const Index nextReadIndex = currentReadIndex + 1;
          if(currentReadIndex == currentWriteIndex)
          {
            return false;
          }
    
          element = elements[currentReadIndex];
    
          if(readIndex.compare_exchange_strong(
            currentReadIndex, nextReadIndex))
          {
            return true;
          }
        }
      }
    };
    
     bool push(const Element & element)
      {
        while(true)
        {
          const Index currentReadIndex = readIndex.load();
          Index currentWriteIndex = writeIndex.load();
          const Index nextWriteIndex = currentWriteIndex + 1;
          if(nextWriteIndex == currentReadIndex)
          {
            return false;
          }
    
          if(scratchIndex.compare_exchange_strong(
            currentWriteIndex, nextWriteIndex))
          {
            elements[currentWriteIndex] = element;
            // Problem here!
            writeIndex = nextWriteIndex;
            return true;
          }
        }
      }