两个不同的进程在同一地址上有 2 std::atomic 个变量?
Two Different Processes With 2 std::atomic Variables on Same Address?
我阅读了 C++ 标准 (n4713) 的 § 32.6.1 3:
Operations that are lock-free should also be address-free. That is,
atomic operations on the same memory location via two different
addresses will communicate atomically. The implementation should not
depend on any per-process state. This restriction enables
communication by memory that is mapped into a process more than once
and by memory that is shared between two processes.
所以听起来好像可以在同一内存位置上执行无锁原子操作。我想知道怎么做。
假设我在 Linux 上有一个命名的共享内存段(通过 shm_open() 和 mmap())。例如,如何对共享内存段的前 4 个字节执行无锁操作?
起初,我以为我可以 reinterpret_cast
指向 std::atomic<int32_t>*
的指针。但后来我读了 this。它首先指出 std::atomic 可能不具有相同大小的 T 或对齐方式:
When we designed the C++11 atomics, I was under the misimpression that
it would be possible to semi-portably apply atomic operations to data
not declared to be atomic, using code such as
int x; reinterpret_cast<atomic<int>&>(x).fetch_add(1);
This would clearly fail if the representations of atomic and int
differ, or if their alignments differ. But I know that this is not an
issue on platforms I care about. And, in practice, I can easily test
for a problem by checking at compile time that sizes and alignments
match.
不过,在这种情况下我没问题,因为我在同一台机器上使用共享内存,并且在两个不同的进程中投射指针将 "acquire" 相同的位置。但是,文章指出编译器可能不会将转换后的指针视为指向原子类型的指针:
However this is not guaranteed to be reliable, even on platforms on
which one might expect it to work, since it may confuse type-based
alias analysis in the compiler. A compiler may assume that an int is
not also accessed as an atomic<int>
. (See 3.10, [Basic.lval], last
paragraph.)
欢迎任何意见!
C++ 标准本身不关心多进程,所以不可能有任何正式的答案。这个答案将假设程序在同步方面与进程的行为或多或少与线程相同。
第一个解决方案需要 C++20 atomic_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;
或使用 C11 atomic_load
and atomic_store
volatile int* i = (volatile int*)shared_mem;
atomic_store(i, 42);
int i2 = atomic_load(i);
是的,C++ 标准对这一切有点含糊其词。
如果您在 Windows(您可能不是),那么您可以使用 InterlockedExchange()
等,它们提供所有必需的语义并且不关心引用对象在哪里(很长 *).
在其他平台上,gcc 有一些 atomic builtins 可能对此有所帮助。它们可能会使您摆脱标准制定者的专制统治。问题是,很难测试生成的代码是否防弹。
在所有主流平台上,std::atomic<T>
确实与 T
具有相同的大小,但如果 T
的 alignof < sizeof.
可能会有更高的对齐要求
您可以通过以下方式检查这些假设:
static_assert(sizeof(T) == sizeof(std::atomic<T>),
"atomic<T> isn't the same size as T");
static_assert(std::atomic<T>::is_always_lock_free, // C++17
"atomic<T> isn't lock-free, unusable on shared mem");
auto atomic_ptr = static_cast<atomic<int>*>(some_ptr);
// beware strict-aliasing violations
// don't also access the same memory via int*
// unless you're aware of possible issues
// also make sure that the ptr is aligned to alignof(atomic<T>)
// otherwise you might get tearing (non-atomicity)
在这些不正确的奇异 C++ 实现中,想要在共享内存上使用您的代码的人将需要做其他事情。
或者如果所有从所有进程访问共享内存一致使用atomic<T>
那么没有问题,你只需要无锁来保证无地址。 (您确实需要检查一下:std::atomic 使用锁的散列 table 用于非无锁。这是地址相关的,单独的进程将有单独的散列 table锁。)
我阅读了 C++ 标准 (n4713) 的 § 32.6.1 3:
Operations that are lock-free should also be address-free. That is, atomic operations on the same memory location via two different addresses will communicate atomically. The implementation should not depend on any per-process state. This restriction enables communication by memory that is mapped into a process more than once and by memory that is shared between two processes.
所以听起来好像可以在同一内存位置上执行无锁原子操作。我想知道怎么做。
假设我在 Linux 上有一个命名的共享内存段(通过 shm_open() 和 mmap())。例如,如何对共享内存段的前 4 个字节执行无锁操作?
起初,我以为我可以 reinterpret_cast
指向 std::atomic<int32_t>*
的指针。但后来我读了 this。它首先指出 std::atomic 可能不具有相同大小的 T 或对齐方式:
When we designed the C++11 atomics, I was under the misimpression that it would be possible to semi-portably apply atomic operations to data not declared to be atomic, using code such as
int x; reinterpret_cast<atomic<int>&>(x).fetch_add(1);
This would clearly fail if the representations of atomic and int differ, or if their alignments differ. But I know that this is not an issue on platforms I care about. And, in practice, I can easily test for a problem by checking at compile time that sizes and alignments match.
不过,在这种情况下我没问题,因为我在同一台机器上使用共享内存,并且在两个不同的进程中投射指针将 "acquire" 相同的位置。但是,文章指出编译器可能不会将转换后的指针视为指向原子类型的指针:
However this is not guaranteed to be reliable, even on platforms on which one might expect it to work, since it may confuse type-based alias analysis in the compiler. A compiler may assume that an int is not also accessed as an
atomic<int>
. (See 3.10, [Basic.lval], last paragraph.)
欢迎任何意见!
C++ 标准本身不关心多进程,所以不可能有任何正式的答案。这个答案将假设程序在同步方面与进程的行为或多或少与线程相同。
第一个解决方案需要 C++20 atomic_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;
或使用 C11 atomic_load
and atomic_store
volatile int* i = (volatile int*)shared_mem;
atomic_store(i, 42);
int i2 = atomic_load(i);
是的,C++ 标准对这一切有点含糊其词。
如果您在 Windows(您可能不是),那么您可以使用 InterlockedExchange()
等,它们提供所有必需的语义并且不关心引用对象在哪里(很长 *).
在其他平台上,gcc 有一些 atomic builtins 可能对此有所帮助。它们可能会使您摆脱标准制定者的专制统治。问题是,很难测试生成的代码是否防弹。
在所有主流平台上,std::atomic<T>
确实与 T
具有相同的大小,但如果 T
的 alignof < sizeof.
您可以通过以下方式检查这些假设:
static_assert(sizeof(T) == sizeof(std::atomic<T>),
"atomic<T> isn't the same size as T");
static_assert(std::atomic<T>::is_always_lock_free, // C++17
"atomic<T> isn't lock-free, unusable on shared mem");
auto atomic_ptr = static_cast<atomic<int>*>(some_ptr);
// beware strict-aliasing violations
// don't also access the same memory via int*
// unless you're aware of possible issues
// also make sure that the ptr is aligned to alignof(atomic<T>)
// otherwise you might get tearing (non-atomicity)
在这些不正确的奇异 C++ 实现中,想要在共享内存上使用您的代码的人将需要做其他事情。
或者如果所有从所有进程访问共享内存一致使用atomic<T>
那么没有问题,你只需要无锁来保证无地址。 (您确实需要检查一下:std::atomic 使用锁的散列 table 用于非无锁。这是地址相关的,单独的进程将有单独的散列 table锁。)