硬件应用程序使用共享内存 (C++) 时需要互斥锁

Posted

技术标签:

【中文标题】硬件应用程序使用共享内存 (C++) 时需要互斥锁【英文标题】:Need for Mutex while using Shared Memory (C++) for hardware applications 【发布时间】:2016-07-11 18:15:43 【问题描述】:

我有一个传感器正在以 n Hz 的频率(比如 10Hz=每秒 10 次)将数据写入线程中的共享内存。一个单独的线程正在读取这些数据并使用它来获取一些结果。读者线程的频率不同。它可以更慢,例如每秒 8 次或更快,例如每秒 15 次,具体取决于正在计算的内容。阅读器线程只是从共享内存中读取数据。它不会修改数据(仅处理它以获得一些结果)并且不会将任何内容写入共享内存。整个过程非常巧妙。我不关心同步,因为阅读器只是在需要时读取共享内存中的内容(它轮询数据)。如果在两次读取之间,共享内存的内容发生了变化,则读取器使用新数据。如果在两次读取之间共享内存的内容没有改变(如果读取器比写入器快得多),那么读取器只需使用共享内存中的任何数据。

现在我的同事告诉我使用互斥锁同步对共享内存的访问,但我不同意。原因是如果我使用互斥锁来控制访问,写入共享内存的写入频率会有所降低(当读取线程锁定互斥锁并写入时)。将来我们会有更多的读取线程,我担心写入线程可以写入共享内存的频率会进一步降低,因为将会有两个线程竞争互斥锁。

我知道竞态条件等,但我觉得竞态条件和 SO 以及其他网站上给出的大量示例考虑的场景与我的不同:例如两个线程正在读取和处理银行余额并且一个线程更慢或更快在阅读和余额金额最终是错误的......导致 2000 美元而不是 1000 美元。但是,就我而言,“银行余额”-要共享的数据是由传感器生成的。值的任何变化都是由于物理原因,要共享的数据值永远不会跳跃这么大。

更多细节:传感器是距离测量传感器。它每秒测量距离 10 次。假设 t=1.0s 时的距离是 10cm,它被写入内存。阅读器读取显示为 10 厘米的共享内存。现在,如果在阅读器读取或处理数据时实际距离发生变化,它将是 10.1 厘米,或者因为距离永远不会大幅跳跃。在下一次轮询中,读取器将读取 10.1 厘米的距离(假设对象是静止的。)这样,我的写入器线程可以尽可能快地写入,而无需等待互斥锁被解锁。

我的推理有问题吗?我能想象的唯一问题是,如果我的编写器和读取器线程试图同时访问内存。但是,调度程序应该在指令之间切换,对吧?也就是说,它只是伪并行处理,对吗?这意味着他们不能同时访问内存,对吗?

【问题讨论】:

可能想在您的问题中添加 TL;DR 部分。 我认为你是完全正确的,只要你只有一个线程写你是安全的。 一般来说,您应该使用共享内存的保护(例如互斥锁)。但是,如果您正在为特定平台编码并且不需要可移植代码并且数据类型是简单(本机)类型,则您可以不用互斥锁。这需要对您的系统有低水平的了解。 【参考方案1】:

我不知道这个答案是否应该是评论,如果是,请告诉我...

您可以尝试实现一个循环缓冲区。这样,写入器就有一个指针,它只是在缓冲区中旋转并继续写入。读者也一样,只是必须“落后于”作者。

这意味着当编写器写入一些值时,它必须增加表明有多少数据可用的变量。当读者阅读样本时,它必须减少这个变量。这些操作必须锁定在互斥体中。虽然 i++ 和 i-- 是原子操作,但在多核系统上,这仍然会造成麻烦,我发现这一点很困难。

所以是的,您确实需要互斥锁,但是因为它只需要一个变量,所以它不会使您的整个程序减慢很多...

【讨论】:

【参考方案2】:

在您的情况下,问题是读取器可以在写入变量的同时读取,问题只是写入,所以我建议您对此写入使用原子操作,这样您就不需要互斥锁。如果数据对齐,则读取是原子的(请参阅Read and Write atomic operation implementation in the Linux Kernel),我不确定写入操作,但也许它们不是那么让我们可以做些什么:

在 C++ 中,STL 提供了一些材料来保证操作是原子的,请参阅:http://en.cppreference.com/w/c/atomic 在 C 中,我发现标准 http://www.gnu.org/software/libc/manual/html_node/Atomic-Types.html 中定义的这种类型 sig_atomic_t 保证了读写的原子操作,它应该可以在没有互斥锁的情况下解决问题。

【讨论】:

这是非常重要和有用的东西。谢谢。 sig_atomic_t 类型对我来说是新的,看起来非常有用,但在这种情况下,我使用的是双精度型。我正在做一个 memcpy 来写入数据,所以我会尝试使它成为原子的。已经看过一两页关于如何使 memcpy 原子化的内容。【参考方案3】:

如果您使用抢先任务(例如中断),则取决于您的实现。读取线程可能正在读取值并且在读取期间被写入线程中断。在您的情况下,我假设该值只是一个整数,因此它并不那么重要。只需确保在读取线程中每次执行只读取一次数据(请参阅原子操作)。如果该值大于一个寄存器值。您可以通过使用队列和多级缓冲区来避免互斥锁。但这会增加您的内存使用量。在您的情况下:如果您的数据大于一个整数,我建议使用三重缓冲内存。在这种情况下,您拥有三个缓冲区,值被写入第一个缓冲区,完成后缓冲区与第二个缓冲区切换,而您的读取线程可以读取第三个缓冲区。

【讨论】:

以上是关于硬件应用程序使用共享内存 (C++) 时需要互斥锁的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python 进程中访问由 C++ 进程创建的互斥锁?

一个用户崩溃时共享内存中的互斥锁?

在我的情况下是不是需要互斥锁[关闭]

仅读取共享内存时的互斥锁

IPC:共享内存终止进程通知

C++关于锁的总结