后来获取自旋锁的线程能否在时间轴上先发生?

问题描述 投票:0回答:1

考虑这个例子:

#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++ multithreading language-lawyer atomic
1个回答
0
投票

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
的调用发生在返回
t2
的调用之前([intro.multithread]),并且这两个调用都发生在
C1​::​time_point​::​max()
之前。 [...]

[在表 103 中]

C1​::​is_steady
|
const bool
|
true
如果
t1 <= t2
始终为 true 并且时钟周期之间的时间恒定,否则为 false。

您的自旋锁具有正确的获取-释放顺序,这样解锁与锁定同步,并且一个关键部分发生在另一个关键部分之前。 因此,如果将

std::time()
替换为
std::chrono::steady_clock::now()
,那么如果 t1 观察到
i == 0
,我们可以得出结论,t1 中的临界区发生在 t2 中的临界区之前,因此 t1 中打印的时间必须不大于时间打印在 t2 中。 (它们当然可以相等。)

© www.soinside.com 2019 - 2024. All rights reserved.