考虑这个例子:
#include <iostream>
#include <atomic>
#include <thread>
#include <chrono>
int main(){
std::atomic<int> v = 0;
std::thread t1([&](){
if(v.exchange(1,std::memory_order::acq_rel) == 2){ // #1
std::cout<<"t1\n"; #2
}
});
std::this_thread::sleep_for(std::chrono::seconds(24 * 60 * 60)); // #3
std::thread t2([&](){
if(v.exchange(2,std::memory_order::acq_rel) == 0){ // #4
std::cout<<"t2\n"; // #5
}
});
t1.join();
t2.join();
}
在此示例中,
#3
使 #4
在时间线中的 #1
之后执行。 #3
的参数表示#4
在24小时后执行。从C++标准的角度来看,我们是否有可能看到以下结果?
t1
t2
即先打印
t1
,24小时后打印t2
或者,也可以
t2
t1
即
t2
和t1
都会在24小时后打印。在这两个结果中,按 #4
的修改顺序,#1
位于 v
之前。
理论上一致的实现可以产生这样的两种结果吗?也就是说,按照原子对象的修改顺序,时间轴中较晚执行的操作排在较早执行的操作之前。
如果您附加调试器并以任意延迟单步执行每个线程,则一致的实现仍然是一致的。
或者例如在调用
std::cout <<
之前设置断点,并在两个线程都到达断点后让它们按任一顺序继续。 或者只是在 std::cout<<"t1\n";
语句上设置断点,然后在打印 t2
字符串后继续一段时间。
原子存储对原子加载的可见性的“合理”和“有限”时间保证只是“应该”而不是“必须”,因为像这样的可能性,或者极端的系统负载,或者进程或系统被挂起/resumed 等。目的是 C++ 实现不应该使最好的情况比需要的情况更糟糕,并且不应该引入无限/无限制的延迟(例如通过编译时)跨潜在无限循环的重新排序操作)。 在实际实现中,正常情况仅取决于硬件,如果您非常努力,可能会使负载花费几微秒的时间才能看到存储,而不是两个线程同时运行时通常需要数十纳秒的核心间延迟运行在不同的核心上。 (例如,存储缓冲区充满了必须在此之前提交的缓存未命中存储。)
但是 ISO C++ 绝对不会提供任何类型的实时最坏情况延迟保证,所以不,24 小时睡眠 (#3
)
保证
#4
是在 #1
之后“安排”的。 (或者原子操作和 std::cout<<
之间没有任意延迟,这就是我上面谈到的,所以我不必遵循检查条件的逻辑来看看它们是否会如果交换以相反的顺序进行连贯排序,则两者仍然成立。)