当消费者在实时上下文中操作时,如何在共享内存中安全地实现环形缓冲区

问题描述 投票:0回答:1

我的情况是这样的:在 Linux 机器上,我有一个共享内存区域,其中包含音频样本的环形缓冲区。该环形缓冲区的消费者是一个硬实时 Xenomai 音频回调,因此不允许执行任何类型的互斥锁定等。生产者是一个常规 Linux 进程,通过

SCHED_FIFO
调度程序进行调度,但除此之外允许为所欲为。

这是我用来实现共享内存环缓冲区的 C++ 类的(非常简化的)实现:

const size_t NUM_SAMPLES_PER_RENDER_BUFFER = 1024;  // producer renders 1024 audio samples at a time
const size_t NUM_RENDER_BUFFERS            = 4;     // quadruple-buffering, for now

struct SharedMemoryAudioData
{
public:
   std::atomic<uint64_t> _totalSamplesProduced; /* since program start */
   std::atomic<uint64_t> _totalSamplesConsumed; /* since program start */

   float _samplesRingBuffer[NUM_SAMPLES_PER_RENDER_BUFFER*NUM_RENDER_BUFFERS];
};

...消费者回调的操作如下(伪代码):

void RealTimeAudioCallback(float * writeSamplesToHere, uint32 numSamplesToWrite)
{
   const size_t ringBufSize = NUM_SAMPLES_PER_RENDER_BUFFER*NUM_RENDER_BUFFERS;
   size_t readIdx = (size_t) (sharedData._totalSamplesConsumed.load() % ringBufSize);
   for (size_t i=0; i<numSamplesToWrite; i++)
   {
      writeSamplesToHere[i] = sharedData._samplesRingBuffer[readIdx++];
      if (readIdx >= ringBufSize) readIdx = 0;
   }
   sharedData._totalSamplesConsumed = sharedData._totalSamplesConsumed.load() + numSamplesToWrite;
}

生产者代码稍微复杂一些;它定期运行并比较

_totalSamplesProduced
_totalSamplesConsumed
并确定适合写入共享内存区域的样本数以保持环形缓冲区填满(即,它会写入尽可能多的样本,而不会冒覆盖消费者当前正在读取的环形缓冲区的区域)。

这一切似乎运行良好,并且对

_totalSamplesProduced
_totalSamplesConsumed
成员变量的访问是并发安全的,因为它们都是
std::atomic

我的问题是关于访问

_samplesRingBuffer
本身的值的安全性。它们也由生产者线程写入并由消费者线程读取,但它们不是
std::atomic
,因此这似乎是技术上未定义的行为。一种解决方案是将数组的类型更改为
std::atomic<float>
,但我怀疑这可能会显着增加性能成本,所以我宁愿不这样做。

我还应该在这里做些什么来帮助使对

_sampleRingBuffer
的共享访问更加并发安全,同时仍然保持高效率? (我意识到制作人总是有可能不及时制作,这会导致欠载并且可能出现音频故障,但实际上似乎不会发生,无论如何我不认为有我能做些什么吗)

c++ shared-memory circular-buffer lockless xenomai
1个回答
0
投票

问题是,如果生产者不接触

_totalSamplesConsumed
并且消费者不接触
_totalSamplesProduced
:那么他们永远不会同步任何东西。在这种情况下,访问样本确实是一场数据竞赛。

但是,消费者应该阅读

_totalSamplesProduced
,因为它应该限制样本以读取可用的样本。这样做足以同步生产者和消费者,并且可以安全地使用生产者编写的样本。

生产者释放,消费者获取也足够了,

_totalSamplesProduced
,不需要你现在使用的默认顺序一致性。

另一个错误:

sharedData._totalSamplesConsumed =
  sharedData._totalSamplesConsumed.load()
  + numSamplesToWrite;

不是原子的。请使用

fetch_add
来代替。

© www.soinside.com 2019 - 2024. All rights reserved.