C++ C++;std::内存\u顺序\u松弛和跳过/停止标志
使用C++ C++;std::内存\u顺序\u松弛和跳过/停止标志,c++,multithreading,stdatomic,memory-barriers,C++,Multithreading,Stdatomic,Memory Barriers,使用std::memory\u order\u 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
std::memory\u order\u 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);
}
constepr静态const std::size\u t capacity=128;
标准:原子活度[容量];
T数据[容量];//不变的/原子的/等等。
模板
无效迭代(闭包&闭包){
对于(std::size_t i=0;i假设:当您使用erase
时,其他线程可以在任何时候运行iterate()
(问题的早期版本没有指定不可变。如果更新数据[i]
的锁定(或缺少锁定)未按顺序写入alive[i],此答案仍然相关
)
如果数据确实是不可变的,那么mou-released
肯定是可以的,除非您需要这些存储的全局可见性,以便根据线程正在执行的其他操作来排序。mou-released
存储最终将始终对其他线程可见(并且在当前CPU上,将很快做到这一点)
如果您要修改非原子<代码>数据[i] <代码>,而<代码>活着[i] <代码>为false,则需要确保其他线程在修改时不使用其值。这将是C++中的UB,而实际硬件上的实际正确性问题取决于T和Cuth<<代码> >
Acquire semantics将用于迭代
。对数据[i]
的访问逻辑上发生在活动[i]
之后,因为单向屏障的方向正确
但是erase
中的存储是一个问题。在对数据[i]
进行任何修改之前,它需要全局可见。但是允许发布存储与以后的存储重新排序。您需要的是
如果T
是一种原子类型,那么data[i]
的发布存储就可以了。但是不要这样做;如果T
太大而不能成为无锁原子,那就糟糕了
在大多数实现中,seq cst存储也可以工作,但我认为这只是一个实现细节。它通常会产生一个存储+一个完整的asm指令(例如x86 MFENCE)。因此它工作的唯一原因是编译器将seq cst存储实现为存储+thread_fence(seq_cst)
请注意,如果closure
修改data[]
,则iterate
是不安全的,除非一次只能有一个线程调用它。在这种情况下,这有什么意义?因此,您可能应该使用
void iterate(Closure&& closure) const
{ ... }
因此,iterate
只对容器的const
对象起作用。你说的“ok”是什么意思?为什么size
是原子的,为什么每次for
循环的循环迭代都要重新读取它(使用默认的seq\u cst加载)?@PeterCordes更新为更简单的情况。通过询问“ok”“如果在一个线程中执行<代码>擦除< /代码>,那么另一个线程< <代码>迭代< /COD>会看到该标志是否为假,以及多久?我们是否可能在一个线程中调用<代码>擦除< /代码>,但另一个线程没有看到该标志改变了吗?请考虑<代码>数据< /代码>不可变/原子。抱歉,这种不精确性。问题是RATH。er关于alivity
线程间的可见性/传播(刚刚开始了解这一点)。对它的更改对所有线程都是不可见的,或者可以用以前的值错误读取。例如:我们在Thread1中将它设置为false
,但Thread2读取以前的true
值。@tower:Stores toalivity[i]
最终将对所有其他线程中的加载可见。(通常非常快,例如仅生产者CPU中存储缓冲区的长度+消费者CPU中早期读取的无序窗口的大小)。但是,加载的无序执行可能会导致不同的使用者看到不同的值。您必须小心如何定义“同步”因为执行顺序不正确。如果这些线程之间没有其他交互方式,这是一个毫无意义的问题,因为它们之间没有同步。因此,在示例中,这是一种机会主义/推测性擦除:),但如果我切换到acquire-release semantic,它将不会出现错误读数?@tower120:不,如果你没有修改数据[I]
,那么将其称为“false”是没有意义的。线程之间没有顺序。这不是“错”要让迭代< /C>使用对应于<代码>生存> /CONT>值之前的<代码>数据< /代码>值,因为C++标准鼓励快速实现对其他线程可见的存储。您可以假设它不太快可见;它可能会使迭代< /COD>运行到Mak慢些。e确保它没有使用提前加载的数据值。我想,让迭代一个或两个额外的元素是正确的。。。
void iterate(Closure&& closure) const
{ ... }