当一个应用程序写入而另一个应用程序读取时,是否需要互斥锁或信号量?



有 2 个应用程序在不同的内核上运行。一个应用程序写入 4 字节的共享内存位置,另一个内核上的另一个应用程序读取。这两个应用程序同时运行。这个模型在不使用互斥锁或信号量的情况下对我有用。这有什么缺点吗?即使在这种情况下,是否需要互斥锁或信号量?

TL:DR:如果你使用std::memory_order_relaxed加载/存储来std::atomic<uint32_t>,那么在 C/C++ 中是安全的。 在asm中,它在所有现代架构上都是安全的。


在所有"正常"CPU架构上没有互斥锁是"安全的",但它不提供太多的同步。 由于以下限制,您只能将其用于简单的东西:

  • 如果生产者的两个存储发生在读取之间(从读者的角度来看),使用者可能会错过更新。 即使使用者通常比生产者快,读者也可能因为页面错误或上下文切换而进入睡眠状态。 除非您在硬实时操作系统下运行,否则您确实可以保证最大延迟。
  • 使用者可以读取相同的值两次。 如果你的数据天生不受 ABA 问题的影响(生产者从不连续两次存储相同的值,例如,一个不可能在读取之间环绕的计数器),那么这也不是问题

    但是,如果使用者只是在等待新值,则可能应该使用无锁队列(在固定大小的循环缓冲区中)。这让读取器在清空队列时进入睡眠状态,或者生产者可以在填满队列时阻止。 它还允许消费者处理几个项目,而生产者不必醒来一次存储一个项目。 对于单生产者单使用者案例,它可以非常有效地实现,甚至对于多编写器多读取器案例(您必须阻止编写器与其他编写器竞争以产生某种总顺序)也可以非常有效地实现。

    您应该只寻找无锁/无锁队列的库实现,除非您真的确定要让读取器旋转一个值,等待它更改。

通常,这种只写/只读模式仅用于时间戳或某物的"当前值"之类的东西。 读者不会试图看到每个更新,他们只是在需要该值时阅读它。


在主流的现代CPU架构上,对齐的字加载/存储指令是原子的,因此您不会看到"撕裂"(来自两个不同存储的字节混合)。 有关 x86 的详细信息,请参阅为什么 x86 上自然对齐变量上的整数赋值是原子的?。

如果你用asm编写,显然你必须知道机器如何工作的细节。

在 C 或 C++ 中,您当然需要使用_Atomicstd::atomic变量,否则您的程序会出现未定义行为数据竞赛。 它可能碰巧工作,或者共享变量的负载可能会被提升到循环之外。 使用myvar.store(newvalu, std::memory_order_relaxed)将使您的加载/存储几乎与高效的常规整数分配完全相同。 也就是说,如果你的代码碰巧只适用于int,那么将std::atomic<int>memory_order_relaxed一起使用不应该减慢它的速度,甚至可能根本不会改变编译器的 asm 输出。 (但它保证您使用不同的周围代码或优化选项的正确性!

正确遵循 ISO C++11 规则将使这种生产者-消费者模式适用于任何符合C++的实施。 在一些晦涩难懂的平台上,数据竞争是硬件(不仅仅是C++优化器)的问题,您的原子变量将使用互斥锁而不是无锁。

这取决于平台。如果不了解该平台,我们甚至无法想象它是如何失败的。是否需要互斥锁或信号量取决于平台的文档是否说它们是必需的。你不能通过实验来判断 - 走过街道而不看两个方向,没有被车撞到并不表明它是安全的。下一个CPU,下一个编译器,下一个系统库甚至下一次系统升级都可能导致它失败,或者它可能只是很少失败。

相关内容

最新更新