考虑这个例子:
#include <iostream>
#include <atomic>
#include <thread>
struct SpinLock{
std::atomic<bool> state;
void lock(){
bool expected = false;
while(!state.compare_exchange_strong(expected,true,std::memory_order::acquire,std::memory_order::relaxed)){
expected = false;
}
}
void unlock(){
state.store(false,std::memory_order::release);
}
};
int main(){
auto spin_lock = SpinLock{false};
int i = 0;
std::thread t1([&](){
std::this_thread::sleep_for(std::chrono::seconds(1));
spin_lock.lock();
auto time_stamp = std::time(nullptr);
std::court<<"t1 " <<time_stamp <<" "<< i<<"\n"; // #1
spin_lock.unlock();
});
std::thread t2([&](){
spin_lock.lock();
i = 1;
auto time_stamp = std::time(nullptr);
std::court<<"t2 " <<time_stamp<<" "<< i<<"\n"; // #2
spin_lock.unlock();
});
t1.join();
t2.join();
}
从C++标准的角度来看,是否有可能
#1
打印i==0
和代表较晚时间的时间戳值,而#2
打印i==1
和代表较早时间的时间戳值?
如果
i
#1
读取到的值为0
,即state
中对t1
的存储操作在修改顺序上早于t2(即t1
中的CAS操作)赢得比赛,因此它首先获取锁),否则读取的值必须是 1
,因为 lock
中的 t1
会与 t2 中的 unlock
同步。 修改顺序与时间线顺序无关,IIUC,结果是
t1 1729172229 0
t2 1729172228 1
这个结果并不直观,但是从C++标准的角度来看,这是一个可能的结果吗?
C++ 标准并不禁止这样做
std::time
。 事实上,关于 std::time
的行为,它没有说太多,而是遵循 C 标准,该标准没有解决这个问题;所以我们必须假设这是允许的。
但是,它确实禁止
std::chrono::steady_clock
或任何其他时钟类型C
,其中C::is_steady == true
。 请参阅time.clock.req p2:
在表 103 中,
和C1
表示时钟类型。C2
和t1
是t2
返回的值,其中返回C1::now()
的调用发生在返回t1
的调用之前([intro.multithread]),并且这两个调用都发生在t2
之前。 [...]C1::time_point::max()
[在表 103 中]
|C1::is_steady
|const bool
如果true
始终为 true 并且时钟周期之间的时间恒定,否则为 false。t1 <= t2
您的自旋锁具有正确的获取-释放顺序,这样解锁与锁定同步,并且一个关键部分发生在另一个关键部分之前。 因此,如果将
std::time()
替换为 std::chrono::steady_clock::now()
,那么如果 t1 观察到 i == 0
,我们可以得出结论,t1 中的临界区发生在 t2 中的临界区之前,因此 t1 中打印的时间必须不大于时间打印在 t2 中。 (它们当然可以相等。)