5

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

现在,根据我读过的一些东西(Linux音频开发列表中的一个线程,thisthis)我需要某种可以同时读取和写入而无需锁的数据结构,或者,我需要某种跨线程通知系统传递变量。

但是,我看到的一些 示例使用 C++ std 库中的 vanilla 向量,它们从一个线程读取并从另一个线程写入,当我运行程序时,它们运行良好。

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

1 回答 1

8

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

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

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

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

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

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

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

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

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

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

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

于 2012-02-13T06:37:21.227 回答