【问题标题】:Does relaxed memory order effect can be extended to after performing-thread's life?宽松的内存顺序效果可以扩展到执行线程的生命之后吗?
【发布时间】:2015-09-04 13:53:30
【问题描述】:

假设在 C++11 程序中,我们有一个名为 A 的主线程,它启动一个名为 B 的异步线程。在线程 B 中,我们对具有std::memory_order_relaxed 内存顺序的原子变量执行原子存储。然后线程 A 与线程 B 连接。然后线程A 启动另一个名为C 的线程,该线程使用std::memory_order_relaxed 内存顺序执行原子加载操作。线程 C 加载的内容是否可能与线程 B 写入的内容不同?换句话说,这里宽松的内存一致性是否会延续到线程生命周期之后?

为了尝试这个,我编写了一个简单的程序并多次尝试运行它。该程序不会报告不匹配。我在想,因为线程 A 在启动线程时强加了一个顺序,所以不会发生不匹配。但是,我不确定。

#include <atomic>
#include <iostream>
#include <future>

int main() {

    static const int nTests = 100000;
    std::atomic<int> myAtomic( 0 );

    auto storeFunc = [&]( int inNum ){
        myAtomic.store( inNum, std::memory_order_relaxed );
    };

    auto loadFunc = [&]() {
        return myAtomic.load( std::memory_order_relaxed );
    };

    for( int ttt = 1; ttt <= nTests; ++ttt ) {
        auto writingThread = std::async( std::launch::async, storeFunc, ttt );
        writingThread.get();
        auto readingThread = std::async( std::launch::async, loadFunc );
        auto readVal = readingThread.get();
        if( readVal != ttt ) {
            std::cout << "mismatch!\t" << ttt << "\t!=\t" << readVal << "\n";
            return 1;
        }
    }

    std::cout << "done.\n";
    return 0;

}

【问题讨论】:

  • 线程加入是一个同步点,它是一个完整的内存屏障。但是,我希望您注意通过测试来证明多线程世界中的任何内容:)
  • @SergeyA 这是一个非常好的观点!我会将其添加到我的答案中。
  • @SergeyA 这就是我在 SO 中问这个问题的原因。否则,我会继续我的简单测试:D 感谢您的帮助!
  • “测试显示错误的存在,而不是它们的缺失” :)

标签: c++ multithreading c++11 concurrency atomic


【解决方案1】:

在可移植线程平台通常为您提供指定内存可见性或放置显式内存屏障的能力之前,可移植同步仅通过显式同步(如互斥锁)和隐式同步来完成。

通常,在创建线程之前,会设置一些数据结构,供线程在启动时访问。为了避免仅仅为了实现这种通用模式而必须使用互斥锁,线程创建被定义为隐式同步事件。加入一个线程然后查看它计算的一些结果同样常见。同样,为了避免必须使用互斥锁来实现这种通用模式,加入线程被定义为隐式同步事件。

由于线程的创建和结构被定义为同步操作,加入线程必然发生在线程终止之后。因此,您将看到在线程终止之前必须发生的任何事情。更改一些变量然后创建线程的代码也是如此——新线程必然会看到在它创建之前发生的所有更改。线程创建或终止的同步就像互斥锁上的同步。同步操作会创建这种确保内存可见性的排序关系。

正如 SergeyA 所提到的,您绝对不应该尝试通过测试来证明多线程世界中的某些东西。当然,如果测试失败,这证明你不能依赖你测试的东西。但是,即使测试通过了您能想到的各种测试方式,但这并不意味着它不会在您未测试的某些平台、CPU 或库上失败。你永远无法通过那种测试证明这样的事情是可靠的。

【讨论】:

  • 谢谢!让我担心的一件事是,由于顺序放宽了,处理器认为没有必要将低级缓存中的值一直推送到主内存中。因此,下一个线程可能不一定会看到前一个线程新更新的值。根据您的解释,启动线程可用作更新主内存(或至少所有线程可见的缓存级别)的内存栅栏。
【解决方案2】:

如果您想测试这样的东西,可以使用模型检查器来探索测试用例的所有可能执行(受一些深奥的限制)。
http://plrg.eecs.uci.edu/c11modelchecker.html

【讨论】:

    猜你喜欢
    • 2022-01-23
    • 1970-01-01
    • 2012-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多