如何编写指令重新排序的可观察示例?
How to write observable example for instruction reorder?
举个例子:
#include <thread>
#include <iostream>
int main() {
int a = 0;
volatile int flag = 0;
std::thread t1([&]() {
while (flag != 1);
int b = a;
std::cout << "b = " << b << std::endl;
});
std::thread t2([&]() {
a = 5;
flag = 1;
});
t1.join();
t2.join();
return 0;
}
从概念上讲,flag = 1;
可以在a = 5;
之前重新排序和执行,因此b的结果可以是5或0。
但是,实际上,我无法在我的机器上产生输出 0 的结果。我们如何保证行为或指令的可重复排序?如何具体更改代码示例?
首先:你处于 UB 的土地上,因为有一个竞争条件:flag
和a
都是在没有适当同步的情况下从不同的线程写入和读取的 - 这始终是一场数据竞赛。当您为实现提供这样的程序时,C++标准不会对实现施加任何要求。
因此,没有办法"保证"特定行为。
但是,我们可以查看程序集输出来确定给定的编译程序可以或不能做什么。我没有成功地单独使用重新排序来显示volatile
作为同步机制的问题,但下面是使用相关优化的演示。
下面是一个没有数据争用的程序示例:
std::atomic<int> a = 0;
std::atomic<int> flag = 0;
std::thread t1([&]() {
while (flag != 1);
int b = a;
std::cout << "b = " << b << std::endl;
});
std::thread t2([&]() {
a = 5;
int x = 1000000;
while (x-- > 1) flag = 0;
flag = 1;
x = 1000000;
while (x-- > 1) flag = 1;
flag = 0;
a = 0;
});
t1.join();
t2.join();
https://wandbox.org/permlink/J1aw4rJP7P9o1h7h
事实上,该程序的通常输出是b = 5
(其他输出是可能的,或者程序可能根本不会因"不幸"调度而终止,但没有 UB)。
如果我们改用不正确的同步,我们可以在程序集中看到此输出不再处于可能性范围内(考虑到 x86 平台的保证):
int a = 0;
volatile int flag = 0;
std::thread t1([&]() {
while (flag != 1);
int b = a;
std::cout << "b = " << b << std::endl;
});
std::thread t2([&]() {
a = 5;
int x = 1000000;
while (x-- > 1) flag = 0;
flag = 1;
x = 1000000;
while (x-- > 1) flag = 1;
flag = 0;
a = 0;
});
t1.join();
t2.join();
第二个螺纹主体的组件,如下所示 https://godbolt.org/z/qsjca1:
std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::{lambda()#2}> > >::_M_run():
mov rcx, QWORD PTR [rdi+8]
mov rdx, QWORD PTR [rdi+16]
mov eax, 999999
.L4:
mov DWORD PTR [rdx], 0
sub eax, 1
jne .L4
mov DWORD PTR [rdx], 1
mov eax, 999999
.L5:
mov DWORD PTR [rdx], 1
sub eax, 1
jne .L5
mov DWORD PTR [rdx], 0
mov DWORD PTR [rcx], 0
ret
请注意a = 5;
是如何完全优化的。在编译的程序中,a
没有任何地方有机会将值5
。
正如您在 https://wandbox.org/permlink/Pnbh38QpyqKzIClY 中看到的那样,程序将始终输出 0(或不终止),即使线程 2 的原始C++代码 - 在"朴素"解释中 - 在flag == 1
时总是有a == 5
。
while
循环当然是为了"消耗时间",并给另一个线程一个交错的机会 -sleep
或其他系统调用通常会构成编译器的内存屏障,并可能破坏第二个代码片段的效果。
- 二叉排序树无法编译
- 仅使用绝对值对数组进行排序,并在C++中显示实际值
- C++选择排序算法中的逻辑错误
- 使用C++程序合并排序没有得到正确的输出
- 计算排序向量的向量中唯一值的计数
- 排序算法c++
- 使用2个键的cpp-stl::优先级队列排序不正确
- 将结构向量排序为子组
- 在c++中尝试对对象数组进行排序时,出现std:bad_alloc错误
- 如何对点云数据进行排序
- 对字符串进行排序时,在c++中处理sort()
- 是否有类似std::lower_bound的函数,而不需要排序/分区输入
- 下面是排序算法O(n)吗
- std::sort()函数无法对向量的一部分进行排序
- shell排序中的交换和比较
- clang格式:禁用排序包含
- 显示错误输出的简单数组排序程序
- 为什么我的排序算法会更改数组值
- 试图在c++中对数字列表进行排序
- 如何编写指令重新排序的可观察示例?