我什么时候需要一个无锁数据结构来跨音频应用程序中的线程读取/写入数据?

时间:2012-02-13 03:38:58

标签: c++ audio openframeworks

我的场景是这样的:用户与GUI元素交互,音频回调函数读取UI设置的变量,计算样本并将样本存储在缓冲区(或任何数据结构)中,然后由UI读取缓冲区并绘制波形(在每秒60次的绘制循环中)。

现在,根据我读过的一些内容(Linux音频开发列表中的一个帖子,thisthis)我需要某种可以同时读写的数据结构需要锁定,或者,我需要某种跨线程通知系统来传递变量。

然而,some examples我见过使用C ++ std库中的vanilla向量,它们从一个线程读取并从另一个线程写入,当我运行程序时,它们运行良好。< / p>

  1. 在哪些情况下我需要使用无锁数据结构来进行这种跨线程通信?
  2. 如果我添加另一个线程,例如接收网络IO并需要将数据传递给其他两个线程的MIDI或OSC回调函数,我是否需要担心无锁结构?
  3. 如果回答第二个问题是“是”,那么这将是一个适当的结构吗?

1 个答案:

答案 0 :(得分:6)

如果您的线程正在访问相同的内存(读取或写入),那么您需要使用锁定,或者需要使用无锁数据结构。否则,当多个线程同时访问数据结构时,您的数据结构可能会损坏(或显示已损坏)。

您指向的示例似乎使用了提前分配的固定大小的向量。音频线程正在写入此缓冲区并且UI线程正在读取它,并且两者未同步。由于两者可以完全同时运行,因此UI线程无法保证实际读取的数据;它可能会读取更新N中的一些数据,而某些数据来自更新N + 1。它可能会遗漏一些数据或读取一些数据两次(或更多)。这不是构建音频应用程序的强大方法。它对于简单的可视化应用程序来说“足够好”,因为可视化的结果不需要完美,但它完全不适合录制或回放应用程序。

音频应用程序通常使用无锁数据结构(而不​​是使用锁),因为音频播放具有“实时”要求。如果您的音频缓冲区包含100毫秒的声音,那么您需要每秒填充这些缓冲区10次,否则您的音频播放将会断断续续。另一种说法是每次填充缓冲区时都有100毫秒截止日期

有各种各样的事情可能导致你错过这100ms的截止日期;如果系统太忙,您的进程可能无法调度,或者页面错误可能导致磁盘读取阻塞进程的时间太长,仅举几个例子。如果你试图获得一个锁,但另一个线程持有它超过100毫秒,这将使你错过你的截止日期。这就是为什么使用锁可能对音频应用程序不利;持有锁太久的另一个线程会让你错过截止日期。

使用无锁数据结构无需等待锁定,因此其他线程无法停止进度。它使您的音频I / O最后期限变得更容易。

但是在你对无锁算法过于兴奋之前,你应该知道它们更加微妙,并且需要比锁具更多的专业知识来正确使用。基本上,如果您不是该领域的专家,则不应尝试自己编写无锁算法。也许有一个很好的开源库,它有一些无锁算法实现;我最近没看过。

但是要意识到使用无锁算法所需的额外工作或多或少都会浪费,除非你在音频线程中也非常小心,以避免其他可能的延迟原因。具体做法是:

  • 您的音频线程不能malloc()free()任何内存(大多数malloc / free实现在内部获取全局锁定)

  • 您必须确保音频线程访问的所有内存都没有被分页(例如mlock()

  • 除了声卡之外,您的音频线程不得执行任何I / O,因为I / O调用可以阻止。

除非您非常勤奋并采取所有这些步骤,否则您也可以使用锁定与其他线程共享的数据,但请确保锁定的持续时间非常短。例如,请确保在保持锁定的UI线程中不执行任何malloc()/ free()或阻塞系统调用。