Multithreading 寻找无锁RT安全的单读单写结构

Multithreading 寻找无锁RT安全的单读单写结构,multithreading,real-time,lock-free,Multithreading,Real Time,Lock Free,我正在寻找符合以下条件的无锁设计: 单个写入程序写入结构,而单个读取器读取此结构(此结构已经存在,并且可以安全地同时读/写) 但有时,编写器需要更改结构,然后初始化、切换并写入新结构(类型相同,但内容新) 在下次读卡器读取时,它将切换到这个新结构(如果写卡器乘法切换到一个新的无锁结构,读卡器将丢弃这些结构,忽略它们的数据) 必须重用这些结构,即在写/读/切换操作期间,不允许堆内存分配/释放,用于RT目的 我目前已经实现了一个包含这些结构的多个实例的ringbuffer;但是这个实现受到这样一

我正在寻找符合以下条件的无锁设计:

  • 单个写入程序写入结构,而单个读取器读取此结构(此结构已经存在,并且可以安全地同时读/写)
  • 但有时,编写器需要更改结构,然后初始化、切换并写入新结构(类型相同,但内容新)
  • 在下次读卡器读取时,它将切换到这个新结构(如果写卡器乘法切换到一个新的无锁结构,读卡器将丢弃这些结构,忽略它们的数据)
  • 必须重用这些结构,即在写/读/切换操作期间,不允许堆内存分配/释放,用于RT目的
我目前已经实现了一个包含这些结构的多个实例的ringbuffer;但是这个实现受到这样一个事实的影响:当编写器使用了ringbuffer中存在的所有结构时,没有更多的地方可以从结构中进行更改。。。但是ringbuffer的其余部分包含一些数据,读卡器不必读取这些数据,但写卡器不能重复使用这些数据。因此,ringbuffer不适合此用途


有无锁设计的想法(名称或伪实现)吗?感谢您考虑了这个问题。

您的思路是正确的

线程/进程/处理器之间固定消息的无锁通信

如果有一个生产者和一个消费者,固定大小的环形缓冲区可以用于线程、进程或处理器之间的无锁通信。要执行的一些检查:

head变量仅由生产者写入(作为写入后的原子操作)

尾部变量仅由使用者写入(作为读取后的原子操作)

陷阱:引入大小变量或缓冲区满/空标志;它们通常由生产者和消费者共同编写,因此会给您带来问题

为了这个目的,我通常使用环形缓冲区。我学到的最重要的一课是,的环形缓冲区不能包含超过个元素。这样,头变量和尾变量分别由生产者和消费者编写

大型/可变大小块的扩展 要在实时环境中使用缓冲区,您可以使用内存池(在实时操作系统中通常以优化形式提供)或将分配与使用解耦。我认为后者符合这个问题

如果您需要交换大的块,我建议使用带有缓冲块的池,并使用队列将指针传递到缓冲区。因此,使用第三个队列和缓冲区指针。通过这种方式,可以在应用程序(后台)中完成分配,并且您的实时部分可以访问可变数量的内存

应用程序

while (blockQueue.full != true)
{
    buf = allocate block of memory from heap or buffer pool
    msg = { .... , buf };
    blockQueue.Put(msg)
}

Producer:
   pBuf = blockQueue.Get()
   pQueue.Put()

Consumer
   if (pQueue.Empty == false)
   {
      msg=pQueue.Get()
      // use info in msg, with buf pointer
      // optionally indicate that buf is no longer used
   }

这里有一个。关键是有三个缓冲区,读卡器保留从中读取的缓冲区。写入程序写入其他两个缓冲区之一。碰撞的风险最小。此外,这将扩大。只需使您的成员数组比读卡器的数量加上写卡器的数量多出一个元素

class RingBuffer
{
  RingBuffer():lastFullWrite(0)
  { 
    //Initialize the elements of dataBeingRead to false
    for(unsigned int i=0; i<DATA_COUNT; i++)
    {
      dataBeingRead[i] = false;
    } 
  }

  Data read()
  {
    // You may want to check to make sure write has been called once here
    // to prevent read from grabbing junk data. Else, initialize the elements
    // of dataArray to something valid.
    unsigned int indexToRead = lastFullWriteIndex;
    Data dataCopy;
    dataBeingRead[indexToRead] = true;
    dataCopy = dataArray[indexToRead];
    dataBeingRead[indexToRead] = false;
    return dataCopy;
  }

  void write( const Data& dataArg )
  {
    unsigned int writeIndex(0);

    //Search for an unused piece of data.
    // It's O(n), but plenty fast enough for small arrays.
    while( true == dataBeingRead[writeIndex] && writeIndex < DATA_COUNT )
    {
      writeIndex++;
    }  

    dataArray[writeIndex] = dataArg;

    lastFullWrite = &dataArray[writeIndex];
  }

private:
  static const unsigned int DATA_COUNT;
  unsigned int lastFullWrite;
  Data dataArray[DATA_COUNT];
  bool dataBeingRead[DATA_COUNT];
};
class-RingBuffer
{
RingBuffer():lastFullWrite(0)
{ 
//将dataBeingRead的元素初始化为false

对于(unsigned int i=0;iToo more emphasis不强调。@KennyTM:你说得对。经过编辑。问得好。这是实时系统中的一个常见问题。我想知道是否有现成的方法可以做到这一点。你需要原子数据读/写。像事务一样,我说得对吗?那么你必须使用一些同步原语来保证原子数据访问。你需要两个类似TryLock的结构和功能实例。如果你愿意,我可以描述更多。TryLock是不允许的:在编写器中,新数据在出现故障时决不能删除。我不遵循这里的解释。需要一些校对。感谢你在这方面花费了这么多时间。如果e consumer不运行,生产者和供给线程将遭受饥饿,就像ringbuffer一样,但会消耗所有堆内存。我刚刚解释了实时部分;实时设计是我每天处理的复杂问题。取决于您如何处理异常处理(客户端未运行等)。我无法为您做出决定(丢弃数据、执行异常处理或阻塞)。由于这些是环形缓冲区,您可以控制元素的数量,使其足以满足后台任务的预期延迟。通常,您会让写入程序读取读取索引以进行完全检测,而让读取程序读取写入索引以进行空检测。这仍然会导致争用,但您不需要同时修改大小。一个另一种选择是在每个bucket中都有一个序列计数器,当读写器彼此不接近时,可以避免读写器之间的争用,如liblfds的MCMP队列中所述(在中讨论)。对于单生产者单消费者,您可以将RMW增量用作单独的加载和存储)如果在读取过程中调用write,则read中的lastFullWriteIndex将被write更改,该怎么办?然后将有两个dataBeingRead实例设置为true。