[英]C++ std::memory_order_relaxed and skip/stop flag
Is it ok to use std::memory_order_relaxed
for skip flag, like in iterate
: 是否可以使用
std::memory_order_relaxed
作为跳过标志,就像在iterate
:
constexpr static const std::size_t capacity = 128;
std::atomic<bool> aliveness[capacity];
T data[capacity]; // immutable/atomic/etc.
template<class Closure>
void iterate(Closure&& closure){
for(std::size_t i = 0; i<capacity; i++){
if (!aliveness[i].load(std::memory_order_relaxed)) continue;
closure( data[i] );
}
}
void erase(std::size_t index){
aliveness[index].store(false, std::memory_order_relaxed);
}
Or I should use release/acquire, instead? 或者我应该使用发布/获取,而不是?
aliveness[i]
may become "alive" again. 活着
aliveness[i]
可能会再次变得“活着”。
iterate
and erase
called from multiple threads simuteniously. iterate
并erase
多个线程中的调用。 Consider data
immutable/atomic/synchronized under some other external lock. 在其他一些外部锁定下考虑
data
不可变/原子/同步。
Assumption: other thread(s) can be running iterate()
at any time while you're using erase
. 假设:当您使用
erase
时,其他线程可以随时运行iterate()
。 (An early version of the question didn't specify immutable. This answer is still relevant if the locking (or lack thereof) for updating data[i]
isn't ordered wrt. writes to alive[i]
.) (该问题的早期版本没有指定不可变。如果更新
data[i]
的锁定(或缺少)没有被命令,那么这个答案仍然是相关的。写入alive[i]
。)
If data is truly immutable, then mo_relaxed
is definitely fine, unless you need the global visibility of those stores to be ordered with respect to something else the thread is doing. 如果数据真的是不可变的,那么
mo_relaxed
绝对没问题,除非您需要针对线程所做的其他事情对这些商店的全局可见性进行排序。 mo_relaxed
stores will always eventually become visible to other threads (and on current CPUs, will do so very quickly). mo_relaxed
存储将始终最终对其他线程可见(并且在当前CPU上,将非常快速地这样做)。
If you're going to modify the non-atomic data[i]
while alive[i]
is false, you need to make sure other threads aren't using its value while it's being modified. 如果您要在
alive[i]
为false时修改非原子data[i]
则需要确保其他线程在修改时未使用其值。 That would be UB in C++, and an actual correctness problem on real hardware depending on T
and closure
. 这将是C ++中的UB,以及真实硬件上的实际正确性问题,具体取决于
T
和closure
。
Acquire semantics will work for iterate
. 获取语义将适用于
iterate
。 Access to data[i]
logically happens after alive[i]
, because the one-way barrier is in the correct direction. 访问
data[i]
在逻辑上后会发生alive[i]
因为阻挡单向处于正确的方向。 The acquire-load won't reorder with later loads or stores, only earlier ones . 获取加载不会在以后的加载或存储中重新排序,只能在之前加载或存储 。
But the store in erase
is a problem. 但是
erase
存储是一个问题。 It needs to be globally visible before any modification of data[i]
. 在对
data[i]
进行任何修改之前,它需要全局可见data[i]
。 But release-stores are allowed to reorder with later stores. 但是发布商店可以在以后的商店重新订购。 What you need is a release fence to block reordering of stores in both directions .
你需要的是一个释放围栏,以阻止两个方向的商店重新排序 。
void erase(std::size_t index){
aliveness[index].store(false, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
}
If T
was an atomic type, a release-store to data[i]
would do the trick. 如果
T
是原子类型,对data[i]
的释放存储就可以了。 But don't do that; 但是不要这样做; that will suck if
T
is too large to be lock-free atomic. 如果
T
太大而无法锁定原子,那将会很糟糕。
On most implementations, a seq-cst store would also work, but I think only as an implementation detail. 在大多数实现中,seq-cst存储也可以工作,但我认为仅作为实现细节。 It usually results in a store + a full-barrier asm instruction.
它通常会导致商店+全屏障asm指令。 (eg x86 MFENCE).
(例如x86 MFENCE)。 So it works only because compilers implement seq-cst stores as store +
thread_fence(seq_cst)
. 所以它的工作原理只是因为编译器将seq-cst存储实现为store +
thread_fence(seq_cst)
。
Note that iterate
isn't safe if closure
modifies data[]
, unless only one thread can call it at a time. 请注意,如果
closure
修改data[]
,则iterate
不安全,除非一次只有一个线程可以调用它。 In which case, what's the point of this? 在哪种情况下,这是什么意思? So probably you should use
所以你可能应该使用
void iterate(Closure&& closure) const
{ ... }
so iterate
only works on const
objects of your container. 所以
iterate
仅适用于容器的const
对象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.