两个不同的进程,在同一地址上有 2 个 std::atomic 变量?
Two Different Processes With 2 std::atomic Variables on Same Address?
我读了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);
如果原子和 int 的表示 不同,或者它们的对齐方式不同。但我知道这不是一个 我关心的平台上的问题。而且,在实践中,我可以轻松测试 通过在编译时检查大小和对齐方式来解决问题 火柴。
托,在这种情况下对我来说很好,因为我在同一台机器上使用共享内存,并且在两个不同的进程中转换指针将"获取"相同的位置。 但是,文章指出编译器可能不会将强制转换的指针视为指向原子类型的指针:
但是,这并不能保证是可靠的,即使在以下平台上也是如此 哪一个可能希望它工作,因为它可能会混淆基于类型 编译器中的别名分析。编译器可能假定 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>
您需要确保共享int
具有std::atomic_ref<int>::required_alignment
对齐;通常与sizeof(int)
相同。 通常,您会在结构成员或变量上使用alignas()
,但在共享内存中,布局由您决定(相对于已知的页面边界)。
这可以防止共享内存中存在不透明的原子类型,从而使您能够精确控制其中的确切内容。
<小时 />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
_Atomic int* i = (_Atomic int*)shared_mem;
atomic_store(i, 42);
int i2 = atomic_load(i);
对齐要求在这里是相同的,alignof(std::atomic<int>)
或_Alignof(atomic_int)
。
是的,C++标准对这一切有点胡言乱语。
如果你在Windows上(你可能不是),那么你可以使用InterlockedExchange()
等,它们提供了所有必需的语义,并且不在乎引用的对象在哪里(它是一个长*)。
在其他平台上,gcc 有一些原子内置,可能有助于解决这个问题。 他们可能会把你从标准编写者的暴政中解放出来。 问题是,很难测试生成的代码是否防弹。
在所有主流平台上,std::atomic<T>
确实具有与T
相同的大小,尽管如果T
具有对齐方式<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使用锁的哈希表来实现非无锁。 这是与地址相关的,单独的进程将具有单独的锁哈希表。
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- 在 lambda 表达式中使用 std::atomic
- C++std::atomic在程序员级别保证了什么
- 如果在 2 个线程中使用,是否值得将size_t声明为 std::atomic?
- 在 C++20 之前和之后初始化 std::atomic
- std::atomic 和 std::mutex 的相对性能
- 简单使用 std::atomic 在两个线程之间共享数据
- Port pthread_cond_broadcast to std::atomic
- std::atomic中的任何内容都是免费等待的
- 为什么 std::atomic 构造函数在 C++14 和 C++17 中的行为不同
- std::atomic是如何实现的
- 使用 std::atomic 标志和 std::condition_variable 在工作线程上等待
- 为什么std::atomic的默认构造函数不默认初始化底层存储值
- 读取互斥对象范围之外的volatile变量,而不是std::atomic
- 为什么std::atomic中的所有成员函数都同时出现在有volatile和没有volatile的情况下
- 访问共享内存而不使用易失性、std::atomic、信号量、互斥锁和自旋锁
- 两个不同的进程,在同一地址上有 2 个 std::atomic 变量?
- 原子读取,然后使用 std::atomic 写入
- std::atomic::fetch_add是x86-64上的串行化操作
- 实际上,C++11 中 std::atomic 的内存占用量是多少?