我无法处理内存障碍的话题。什么时候,哪些线程,以什么顺序更新共享变量的值?
bool flag = false;
void f1() {
flag = true;
}
void f2() {
cout << flag << endl;
}
int main() {
thread FIRST_THREAD(f1); //Первый поток
thread SECOND_THREAD(f2); //Второй поток
FIRST_THREAD.join();
SECOND_THREAD.join();
}
是否会发生在第一个线程执行f1()后更改flag为true第二个线程执行f2()后读取旧值flag并打印false?
如果是这样,应该如何设置内存屏障来避免这种情况?所以?:
void f1() {
atomic_thread_fence(memory_order_release);
flag = true;
}
void f2() {
cout << flag << endl;
atomic_thread_fence(memory_order_acquire);
}
它与以下有何不同:
void f1() {
atomic_thread_fence(memory_order_seq_cst);
flag = true;
}
void f2() {
cout << flag << endl;
atomic_thread_fence(memory_order_seq_cst);
}
据我所知,该变体release/acquire比seq_cst. “较弱”是什么意思?
升级版:
@Andrej Levkovitch,首先,我自己不知道我的问题是否有问题,所以我问的第一件事是“会不会是……?” 找出是否有问题。我马上会注意到,问题不在于flag通过互斥锁、原子变量等同步访问。在我描述的假设线程执行场景中,默认情况下会发生访问同步flag:第一个线程总是flag在第二个线程读取之前写入flag。问题不同:第一个线程能否将值更改flag为,之后true的第二个线程(即有同步)不更新其值并读取旧值flagfalse. “在家”是指(虽然我不确定我在这里写的是什么)线程正在运行的处理器内核的缓存。
一些重要的非显而易见的事实
std::atomic_thread_fence允许您使这些操作更加优化。std::atomic_thread_fence。没有它们,语言不提供任何保证。什么时候需要内存屏障?
假设我们有两个绑定变量:
在一个线程中,分配发生(IRL 可以被认为是原子的):
在第二个线程中,检查值:
在市侩级别,似乎应该始终显示该行。在第一个线程中,首先
ready赋值,然后才赋值msg。但是,首先,编译器有权重新排列赋值msg和ready位置,其次,一些处理器本身也可以在运行时重新排列这些操作;所以第二个线程可能会发现自己处于ready == 1, 和msg == 0.内存屏障正是编译器和处理器都不会重新排列操作的界线。那些。第一个线程应如下所示:
在第二个流中,还需要一个屏障,因为。编译器(或处理器,更有可能)可以把它变成这样的东西:
因此,您需要执行以下操作:
为什么
std::atomic_thread_fence不是每一步都使用它?事实是,一方面,所有
memory_order_relaxed使用原子变量 (std::atomic) 的操作(除了那些显式标记的操作)都会隐式生成内存屏障,另一方面,使用std::atomic_thread_fence需要同时使用原子变量。print ()例如,从上面的示例中绝对正确,如下所示:那为什么还需要它
std::atomic_thread_fence呢?它允许您给编译器和处理器更多的优化自由度。而对于程序员来说,更准确地描述他想要什么。例如
atomic_print (),从上面的示例中,它实际上执行以下操作序列(伪代码):这里的问题是,
fence()无论那里是否需要访问,它都会执行msg,并且在某些架构师中,这条指令可能非常昂贵。std::atomic_thread_fence让我们将其重写为:release / acquire / seq_cst 障碍有什么区别?
不同之处在于编译器可以通过它们重新排列哪些指令。
seq_cst- 最严格的。它保证不会通过它重新排列任何指令(既不保存也不加载)(既不向前也不向后)。release- 确保屏障之后的所有指令(读取和写入)不会在屏障之前的写指令 之前重新排列:acquire- 相反,它保证在屏障之前的任何指令(无论是读取还是写入)都不会在屏障之后的任何读取指令 之后传递:什么时候用什么?
seq_cst。在其他情况下(例如,在创建加载的非阻塞原语时),简而言之,通常值得在保存时(在保存的最后)release和加载时(在开始时)使用 -acquire。实际上,学习区分何时使用一种语义以及何时使用另一种语义是创建非阻塞系统时的主要任务之一。一字答题
一般来说是可以的,但是这里没关系。这种情况与
f2()在 f1() 之前执行过时的变体无法区分。它对编译器/处理器施加的限制更少。
假设它可以,但我无法说出会发生这种情况的真实架构。在 x86 上,它绝对不能。
有趣的信息。
首先,我想说,使用全局变量(在绝大多数情况下)是一种恶毒的做法。并且全局变量是由线程共享的——一般来说,你应该有一个很好的理由。但是既然它发生了,而且你需要共享一些简单类型的变量(意思是 int、bool、...),那么最好使用 atomic。标准库包含 bool 的原子 - 在这种情况下,这将是最好的解决方案。如果你需要共享某个类的变量,那么你已经需要和互斥锁一起使用了:锁定与变量关联的互斥锁并改变变量。