松弛原子与无同步情况下的记忆连贯性



我已经编写了一个基本的图调度程序,它以无等待的方式同步任务执行。由于图拓扑是不可变的,我想我会放松所有的原子操作。然而,随着我对CPU硬件的了解越来越多,我开始担心我的数据结构在内存模型较弱的平台上的行为(我只在x86上测试过我的代码(。以下是困扰我的场景:

线程1(T1(和线程2(T2(分别并发地(非原子地(更新内存位置X和Y,然后继续执行其他不相关的任务。

线程3(T3(在T1和T2完成后拾取一个相关任务,加载X和Y,并将它们相加。不存在正在调用的获取/释放同步、线程联接或锁,并且T3的任务保证在T1和T2完成之后进行调度。

假设T1、T2和T3是(由操作系统(在不同的CPU内核上调度的,我的问题是:在没有任何内存围栏或类似锁的指令的情况下,T3是否保证能看到X和Y的最新值另一种提问方式是:如果你不插入围栏,商店多久后你才能进行装载,或者对此没有保证吗

我担心的是不能保证执行T1和T2的内核在T3的内核尝试加载该信息时已经刷新了它们的存储缓冲区。我倾向于将数据竞赛视为由于加载和存储(或存储和存储(同时发生而发生的数据损坏。然而,我逐渐意识到,考虑到CPU在微观规模上的分布式性质,我不太确定同时到底意味着什么。根据CppRef:

具有两个冲突求值的程序具有数据竞赛,除非:

  • 两个求值都在同一线程或同一信号处理程序中执行,或者
  • 两个冲突的求值都是原子操作(请参见std::atomic(,或者
  • 其中一个冲突的求值发生在另一个求值之前(请参见std::memoryorder(

这似乎意味着,任何使用我的图调度程序的人都会经历数据竞赛(假设他们自己没有保护(,即使我可以保证T3在T1和T2完成之前不会执行。我还没有在测试中观察到数据竞赛,但我并不天真地认为仅靠测试就足以证明这一点。

在一个商店之后,您可以执行多长时间的加载

ISO C++对时间没有任何保证。依靠时间/距离来保证正确性几乎总是个坏主意。

在这种情况下,您所需要的只是在调度器本身的某个位置获取/释放同步,例如T1和T2声明它们使用发布存储完成,并且调度器使用获取负载来检查这一点。

否则,T3在T1和T2之后执行意味着什么如果调度器可以很早地看到"I’m done"存储,那么它可以启动T3,而T1或T2并没有完成所有存储。

如果您确保T3中的所有事情都发生在T1和T2之后(使用与T1和T2中的每个发布存储"同步"的获取负载(,那么您甚至不需要在T1和T2使用原子,只需要在调度器机制中使用原子

与seq_cst相比,获取加载和发布存储相对便宜。在真正的硬件上,seq_cst必须在存储后刷新存储缓冲区,而release则不需要。x86免费提供acq_rel。



我不确定启动一个新的线程是否能保证该线程中的一切都"发生在"该线程的那个点之后。如果是这样,那么这在形式上是安全的。

如果没有,那么在理论上IRIW重新排序是需要担心的。(所有使用seq_cst加载的线程都必须同意seq_cst存储的全局顺序,但不能使用较弱的内存顺序。实际上,PowerPC是在现实生活中可以做到这一点的硬件,AFAIK,并且仅适用于短窗口。其他线程是否总是以相同的顺序看到对不同线程中不同位置的两个原子写入?任何std::thread构造函数都会涉及系统无论ISO C++是否正式保证,在实践中都要有足够的时间,并涉及障碍。

如果不是启动一个新线程,而是存储一个标志供工作人员查看,那么acq/rel就足够了;发生在前面是可传递的,所以A->B和B->C意味着A->C。

最新更新