zoukankan      html  css  js  c++  java
  • std::thread详解

    1. std::thread基本介绍

       1)构造std::thread对象时,如果不带参则会创建一个空的thread对象,但底层线程并没有真正被创建,一般可将其他std::thread对象通过move移入其中;

          如果带参则会创建新线程,而且会被立即运行。

       2)joinable():用于判断std::thread对象联结状态,一个std::thread对象只可能处于可联结或不可联结两种状态之一。

          a. 可联结:当线程己运行或可运行、或处于阻塞时是可联结的。注意,如果某个底层线程已经执行完任务,但是没有被join的话,仍然处于joinable状态。

             即std::thread对象(对象由父线程所有)与底层线程保持着关联时,为joinable状态。

          b. 不可联结:

         ① 当不带参构造的std::thread对象为不可联结,因为底层线程还没创建。

         ② 己移动的std::thread对象为不可联结。

         ③ 己调用join或detach的对象为不可联结状态。因为调用join()以后,底层线程己结束,而detach()会把std::thread对象和对应的底层线程之间的连接断开。

          join():等待子线程,调用线程处于阻塞模式。join()执行完成之后,底层线程id被设置为0,即joinable()变为false。

        detach():分离子线程,与当前线程的连接被断开,子线程成为后台线程,被C++运行时库接管。

       3)std::thread对象析构时,会先判断是否可joinable(),如果可联结,则程序会直接被终止出错。这意味着创建thread对象以后,要在随后的某个地方调用join或

          detach以便让std::thread处于不可联结状态。

       4)std::thread对象不能被复制和赋值,只能被移动。

      5)获取当前信息

    // t为std::thread对象
    t.get_id();                          // 获取线程ID
    t.native_handle();                   // 返回与操作系统相关的线程句柄
    std::thread::hardware_concurrency(); // 获取CPU核数,失败时返回0

       6)std::this_thread命名空间中相关辅助函数

    get_id();                            // 获取线程ID
    yield();                             // 当前线程放弃执行,操作系统转去调度另一线程
    sleep_until(const xtime* _Abs_time); // 线程休眠至某个指定的时刻(time point),该线程才被重新唤醒
    sleep_for(std::chrono::seconds(3));  // 睡眠3秒后才被重新唤醒,不过由于线程调度等原因,实际休眠时间可能比 sleep_duration 所表示的时间片更长
    

    3. 传递参数的方式(2次传参)

       a. 第一次传参(向 std::thread 构造函数传参):在创建thread对象时,std::thread构建函数中的所有参数均会按值并以副本的形式保存成一个tuple对象。

          该tuple由调用线程(一般是主线程)在堆上创建,并交由子线程管理,在子线程结束时同时被释放。

          注:如果要达到按引用传参的效果,可使用std::ref来传递。

       b. 第二次传参(向线程函数的传参):由于std::thread对象里保存的是参数的副本,为了效率同时兼顾一些只移动类型的对象,所有的副本均被

          std::move到线程函数,即以右值的形式传入,所以最终传给线程函数参数的均为右值。

       现在我们根据线程参数的类型展开讨论:

       首先先给出一个用作测试的类

    class Test 
    {
    public:
        mutable int mutableInt = 0;
        Test() : mutableInt(0) { cout << this << " " << "Test()" << endl; }
        Test(int i) : mutableInt(i) { cout << this << " " << "Test(int i)" << endl; }
        Test(const Test& w) : mutableInt(w.mutableInt) { cout << this << " " << "Test(const Test& w)" << endl; }
        Test(Test&& w)  noexcept  // 移动构造
        { 
            mutableInt = w.mutableInt; 
            cout << this << " " << "Test(Test && w)" << endl;
        }
        void func(const string& s) { cout <<"void func(string& s)" << endl; }
    };

       a. 线程参数为const T&类型:这里的引用是针对于第二次传递的参数,也就是直接引用保存在tuple中的参数副本,而不是最原始的参数,即

          不是main中变量。但是你如果第一次向std::thread传参时使用std::ref,首先会创建一个std::ref(它也是一个类)临时对象,里面会保存着最原始

          变量(main中的变量)的引用,然后这个std::ref临时对象再以副本的形式保存在std::thread中,随后这个副本被move到线程函数。由于std::ref

          重载了类型转换运算符operator T&(),因此会隐式转换为Test&类型,因此起到的效果就好象main中的变量直接被按引用传递到线程函数中来。

          现在按照这个理解来分析下面的代码:

          对于std::thread t1(test_ctor, w); 首先会调用一次拷贝构造,把w存储为tuple元素;然后因为线程参数是引用,所以tuple元素给线程传参数时不会发生

          拷贝,但实际运行结果发现多输出了一次拷贝,这应该是std::thread隐藏的实现细节,需要阅读源码了。

          如果使用std::ref包装的话,内部引用了原始的w,所以不会发生拷贝,但会发生std::ref对象的拷贝。

    void test_ctor(const Test& w) { cout << &w << " " << "w.matableInt = " << ++w.mutableInt << endl; }
    
    int main()
    {
        Test w;
    
        // std::thread默认的按值传参方式: 所有的实参都是被拷贝到std::thread对象的tuple中,即以副本形式被保存起来。
        // 注意,w是按值保存到std::thread中的,会调用其拷贝构造函数。外部的w没受影响。mutableInf仍为0。
        std::thread t1(test_ctor, w);
        t1.join();
        cout << "w.mutableInt = " << w.mutableInt << endl << endl;
    
        // std::thread按引用传参(std::ref), 因为w是按引用传入到std::ref对象中的,不会调用其拷贝构造函数。
        // 由于w按引用传递,mutableInf被修改为1。
        std::thread t2(test_ctor, std::ref(w));
        t2.join();
        cout << "w.mutableInt = " << w.mutableInt << endl;
        return 0;
    }
    
    // 第一部分输出如下
    Test(const Test& w)  // 调用拷贝构造函数生成std::thread中的副本对象
    Test(Test && w)      // std::thread中的副本移动到线程参数
    w.mutableInt = 0
    
    // 第二部分
    w.mutableInt = 1

        b. 线程参数为T&类型:最终传给线程函数参数的均为右值,而T&类型是不接受右值的,使用std::ref包装后便不报错了,因为它能隐式转换成 T&。

    void updateTest_ref(Test& w) { cout << &w << " " << "invoke updateTest_ref" << endl; }
    
    int main()
    {
        Test w;
        std::thread t1(updateTest_ref, w);             // 编译失败,因为std::thread内部是以右值形式向线程函数updateTest_ref(Test&)传参的,
                                                       // 而右值无法用来初始化Test&引用。
        std::thread t3(updateTest_ref, std::ref(w));   // ok, 原因类似test_ctor函数中的分析。即当线程函数的形参为T&时,一般以std::ref形式传入
        t3.join();
    }
  • 相关阅读:
    solaris如何启动ssh服务
    网页实现插入图片—css与html的区别
    Python与RPC -- (转)
    Python中的异常处理 -- (转)
    Python的异常处理机制 -- (转)
    HTML 学习
    链表练习 链表反转 链表插入..
    php解决抢购秒杀抽奖等大流量并发入库导致的库存负数的问题
    PHP队列的实现 算法
    利用redis List队列简单实现秒杀 PHP代码实现
  • 原文地址:https://www.cnblogs.com/yanghh/p/12965858.html
Copyright © 2011-2022 走看看