宽松的内存顺序作用是否可以扩展到执行线程的寿命之后? [英] Does relaxed memory order effect can be extended to after performing-thread's life?

查看:101
本文介绍了宽松的内存顺序作用是否可以扩展到执行线程的寿命之后?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设在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所述,您绝对不应尝试通过测试在多线程世界中证明某些东西.当然,如果测试失败,那就证明您不能依赖于所测试的东西.但是,即使您认为可以通过各种方式成功进行测试,这也不意味着它不会在未测试的某些平台,CPU或库上失败.通过这种测试,您永远无法证明这样的事情是可靠的.

Let's say inside a C++11 program, we have a main thread named A that launches an asynchronous thread named B. Inside thread B, we perform an atomic store on an atomic variable with std::memory_order_relaxed memory order. Then thread A joins with thread B. Then thread A launches another thread named C that performs an atomic load operation with std::memory_order_relaxed memory order. Is it possible that thread C loaded content is different from the content written by thread B? In other words, does relaxed memory consistency here extends to even after the life of a thread?

To try this, I wrote a simple program and ran it with many tries. The program does not report a mismatch. I'm thinking since thread A imposes an order in launch of threads, mismatch cannot happen. However, I'm not sure of it.

#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;

}

解决方案

Before portable threading platforms generally offered you the ability to specify memory visibility or place explicit memory barriers, portable synchronization was accomplished exclusively with explicit synchronization (things like mutexes) and implicit synchronization.

Generally, before a thread is created, some data structures are set up that the thread will access when it starts up. To avoid having to use a mutex just to implement this common pattern, thread creation was defined as an implicitly synchronizing event. It's equally common to join a thread and then look at some results it computed. Again, to avoid having to use a mutex just to implement this common pattern, joining a thread is defined as an implicitly synchronizing event.

Since thread creation and structure is defined as a synchronizing operation, joining a thread necessarily happens after that thread terminates. Thus you will see anything that necessarily happened before the thread terminated. The same is true of code that changes some variables and then creates a thread -- the new thread necessarily sees all the changes that happened before it was created. Synchronization on thread creation or termination is just like synchronization on a mutex. Synchronizing operations create this kinds of ordering relationships that ensure memory visibility.

As SergeyA mentioned, you should definitely never try to prove something in the multithreaded world by testing. Certainly if a test fails, that proves you can't rely on the thing you tested. But even if a test succeeds every way you can think of to test it, that doesn't mean it won't fail on some platform, CPU, or library that you didn't test. You can never prove something like this is reliable by that kind of testing.

这篇关于宽松的内存顺序作用是否可以扩展到执行线程的寿命之后?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆