c++ - 在同一地址上具有 2 个 std::atomic 变量的两个不同进程?
问题描述
我阅读了 C++ 标准 (n4713) 的 § 32.6.1 3:
无锁的操作也应该是无地址的。也就是说,通过两个不同地址对同一内存位置的原子操作将以原子方式进行通信。实现不应依赖于任何每个进程的状态。此限制允许通过多次映射到一个进程的内存和两个进程之间共享的内存进行通信。
所以听起来可以在同一个内存位置执行无锁原子操作。我想知道它是怎么做到的。
假设我在 Linux 上有一个命名的共享内存段(通过 shm_open() 和 mmap())。例如,如何对共享内存段的前 4 个字节执行无锁操作?
起初,我以为我可以只reinterpret_cast
指向std::atomic<int32_t>*
. 但后来我读到了这个。它首先指出 std::atomic 可能与 T 或对齐的大小不同:
当我们设计 C++11 原子时,我误以为可以使用以下代码半可移植地将原子操作应用于未声明为原子的数据
int x; reinterpret_cast<atomic<int>&>(x).fetch_add(1);
如果 atomic 和 int 的表示不同,或者它们的对齐方式不同,这显然会失败。但我知道这在我关心的平台上不是问题。而且,在实践中,我可以通过在编译时检查大小和对齐是否匹配来轻松测试问题。
Tho,在这种情况下我很好,因为我在同一台机器上使用共享内存并且在两个不同的进程中转换指针将“获取”相同的位置。但是,文章指出编译器可能不会将转换后的指针视为指向原子类型的指针:
然而,这并不能保证是可靠的,即使在人们可能期望它工作的平台上也是如此,因为它可能会混淆编译器中基于类型的别名分析。编译器可能会假设 int 也不会作为
atomic<int>
. (见 3.10,[Basic.lval],最后一段。)
欢迎任何意见!
解决方案
C++ 标准本身并不关心多进程,因此不可能有任何正式的答案。这个答案将假设程序在同步方面与进程的行为或多或少与线程的行为相同。
第一个解决方案需要 C++20atomic_ref
void* shared_mem = /* something */
auto p1 = new (shared_mem) int; // For creating the shared object
auto p2 = (int*)shared_mem; // For getting the shared object
std::atomic_ref<int> i{p2}; // Use i as if atomic<int>
这可以防止共享内存中存在不透明的原子类型,从而使您可以精确控制其中的确切内容。
C++20 之前的解决方案是
auto p1 = new (shared_mem) atomic<int>; // For creating the shared object
auto p2 = (atomic<int>*)shared_mem; // For getting the shared object
auto& i = *p2;
或使用 C11atomic_load
和atomic_store
volatile int* i = (volatile int*)shared_mem;
atomic_store(i, 42);
int i2 = atomic_load(i);