我什么时候需要无锁数据结构来跨音频应用程序中的线程读取/写入数据?
我的场景是这样的:用户与 GUI 元素交互,音频回调函数读取 UI 设置的变量,计算样本并将样本存储在缓冲区(或任何数据结构)中,然后由 UI 读取缓冲区并绘制波形(每秒绘制循环 60 次)。
现在,根据我读过的一些内容(Linux 音频开发列表中的一个线程,this和这个)我需要某种可以读取和的数据结构同时写不需要锁,或者,我需要某种跨线程通知系统来传递变量。
但是,一些 示例 我已经看到使用 C++ std 库中的普通向量,它们从一个线程读取并从另一个线程写入,当我运行程序时,它们运行良好。
- 在哪些情况下我需要使用无锁数据结构来进行这种跨线程通信?
- 如果我添加另一个线程(例如接收网络 IO 的 MIDI 或 OSC 回调函数)并需要将数据传递给其他两个线程,我是否需要担心无锁结构?
- 如果第二个问题的答案是“是”,那么哪个结构是合适的?
My scenario is something like: User interacts with GUI elements, audio callback function reads variables set by the UI, calculates samples and stores samples in a buffer (or whatever data-structure), buffer is then read by the UI and draws a wave form (in a draw loop 60 times per second).
Now, according to some stuff I've read (a thread in the Linux audio dev list, this and this) I need some sort of data structure that can be read and written simultaneously without requiring a lock, or, I need some sort of cross-thread notification system to pass variables around.
However, some examples I've seen use vanilla vectors from the C++ std library, they read from one thread and write from the other and when I run the programs, they run fine.
- In which cases do I need to use a lock free data structure do do this sort of cross-thread communication?
- If I add another thread such as a MIDI or OSC callback function that receives network IO and needs to pass data to the other two threads, do I need to worry about lock-free structures?
- Which would be an adequate structure to use if answer to number two is "yes"?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果您有访问同一内存(读或写)的线程,那么您要么需要使用锁,要么需要使用无锁数据结构。否则,当多个线程同时访问数据结构时,它们可能会损坏(或看起来已损坏)。
看来您指向的示例使用了提前分配的固定大小向量。音频线程正在写入此缓冲区,而 UI 线程正在读取它,两者不同步。由于两者可能完全并发运行,因此 UI 线程无法保证实际读取的数据;它可能会从更新 N 读取一些数据,并从更新 N+1 读取一些数据。它可能会丢失某些数据或读取某些数据两次(或更多次)。这不是构建音频应用程序的可靠方法。对于简单的可视化应用程序来说,它“工作”得足够好,因为可视化的结果不需要是完美的,但它完全不适合记录或回放应用程序。
音频应用程序经常使用无锁数据结构(而不是使用锁),因为音频播放有“实时”要求。如果您的音频缓冲区包含 100 毫秒的声音,那么您需要每秒填充这些缓冲区 10 次,否则您的音频播放将会卡顿。另一种说法是每次都有 100 毫秒的最后期限来填充缓冲区。
有各种各样的事情可能会导致您错过这 100 毫秒的最后期限;如果系统太忙,您的进程可能无法调度,或者页面错误可能导致磁盘读取阻塞进程太长时间,仅举几个例子。如果您尝试获取锁,但另一个线程持有它超过 100 毫秒,这将使您错过最后期限。这就是为什么使用锁可能对音频应用程序不利的原因;另一个持有锁时间过长的线程可能会让您错过最后期限。
使用无锁数据结构时,无需等待锁,因此其他线程无法阻止您的进度。它可以更轻松地满足您的音频 I/O 截止日期。
但在您对无锁算法过于兴奋之前,您应该知道它们比锁更加微妙并且需要更多的专业知识才能正确使用。基本上,如果您不是该领域的专家,您不应该尝试自己编写无锁算法。也许有一个很好的开源库,有一些无锁算法的实现;我最近没看。
但要意识到,使用无锁算法所需的额外工作或多或少是浪费的,除非您在音频线程中也非常小心以避免其他可能的延迟原因。具体来说:
您的音频线程不得
malloc()
或free()
任何内存(大多数 malloc/free 实现在内部获取全局锁)您必须确保访问的任何内存您的音频线程不会被调出(例如
mlock()
)您的除了声卡之外,音频线程不得执行任何 I/O,因为 I/O 调用可能会阻塞。
除非您非常勤奋并采取所有这些步骤,否则您也可以对与其他线程共享的数据使用锁,但请确保持有锁的时间很短。例如,确保不要在持有锁的 UI 线程中执行任何 malloc()/free() 或阻塞系统调用。
If you have threads that are accessing the same memory (reading or writing), then you either need to use locks or you need to use a lock-free data structure. Otherwise your data structures can become corrupted (or appear corrupted) when they are being accessed by more than one thread at the same time.
It appears that the examples you are pointing to use a fixed-size vector that is allocated ahead-of-time. The audio thread is writing to this buffer and the UI thread is reading it, and the two are not synchronized. Since the two could be running completely concurrently, the UI thread has no guarantees about what data is actually being read; it might read some data from update N and some from update N+1. It might miss some data or read some data twice (or more). This is not a robust way to build audio applications. It will "work" well enough for a simple visualization app because the results of the visualization don't need to be perfect, but it would be totally unsuitable for a recording or playback application.
Audio applications often use lock-free data structures (instead of using locks) because audio playback has "real-time" requirements. If your audio buffers contain 100ms of sound, then you need to fill these buffers 10 times a second or else your audio playback will stutter. Another way of saying it is that you have a 100ms deadline to fill the buffer each time.
There are all sorts of things that can cause you to miss this 100ms deadline; if the system is too busy your process might not get scheduled, or a page fault could cause an disk read that blocks the process for too long, just to name a couple of examples. If you're attempting to acquire a lock but another thread holds it for more than 100ms, this will make you miss your deadline. This is why using locks can be bad for audio applications; another thread that holds the lock for too long can make you miss your deadline.
With a lock-free data structure there is no lock to wait for, so other threads cannot halt your progress. It makes it easier to make your audio I/O deadlines.
But before you get too excited about lock-free algorithms, you should know that they're much more subtle and require much more expertise to use correctly than locks. Basically if you're not a specialist in this area you should not attempt to write lock-free algorithms yourself. Perhaps there is a good open-source library that has some lock-free algorithm implementations; I haven't looked lately.
But realize that the extra work it takes to use lock-free algorithms is more or less wasted unless you are also being extremely careful in your audio thread to avoid other possible causes of delay. Specifically:
your audio thread must not
malloc()
orfree()
any memory (most malloc/free implementations acquire a global lock internally)you must make sure that any memory accessed by your audio thread is not paged out (with eg.
mlock()
)your audio thread must not perform any I/O besides with the sound card, since I/O calls can block.
Unless you are being very diligent and taking all these steps, you might as well use a lock for data that is shared with other threads, but do be sure that the lock is held for very short amounts of time. For example, be sure not to perform any malloc()/free() or blocking system calls in the UI thread with the lock held.