zoukankan      html  css  js  c++  java
  • C++ 多线程(3)std::thread 详解

    @

    一、头文件

    std::thread 在 头文件中声明,因此使用 std::thread 时需要包含 头文件。

    二、std::thread 构造函数

    在这里插入图片描述

    (1). 默认构造函数,创建一个空的 thread 执行对象。
    (2). 初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
    (3). 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
    (4). move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。

    注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached.

    std::thread 各种构造函数例子如下(参考):

    #include <iostream>
    #include <utility>
    #include <thread>
    #include <chrono>
     
    void f1(int n)
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 1 executing
    ";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
     
    void f2(int& n)
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 2 executing
    ";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
     
    class foo
    {
    public:
        void bar()
        {
            for (int i = 0; i < 5; ++i) {
                std::cout << "Thread 3 executing
    ";
                ++n;
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
        }
        int n = 0;
    };
     
    class baz
    {
    public:
        void operator()()
        {
            for (int i = 0; i < 5; ++i) {
                std::cout << "Thread 4 executing
    ";
                ++n;
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
        }
        int n = 0;
    };
     
    int main()
    {
        int n = 0;
        foo f;
        baz b;
        std::thread t1; // t1 is not a thread
        std::thread t2(f1, n + 1); // pass by value
        std::thread t3(f2, std::ref(n)); // pass by reference
        std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
        std::thread t5(&foo::bar, &f); // t5 runs foo::bar() on object f
        std::thread t6(b); // t6 runs baz::operator() on object b
        t2.join();
        t4.join();
        t5.join();
        t6.join();
        std::cout << "Final value of n is " << n << '
    ';
        std::cout << "Final value of foo::n is " << f.n << '
    ';
    }
    

    三、其他成员函数

    指向当前线程std::this_thread
    例如std::this_thread::get_id()

    get_id 获取线程 ID。
    joinable 检查线程是否可被 join。
    join Join 线程。
    detach Detach 线程
    swap Swap 线程 。
    native_handle 返回 native handle。
    hardware_concurrency [static] 检测硬件并发特性

    四、传递临时参数作为线程对象的注意事项

    注意:以下问题主要都是在detach情况下发生,join下不会发生。

    			   线程(函数)的传入参数,引用&会失效,指针*还是会传递地址。
    
    #include <iostream>
    #include <thread>
    #include <string>
    using namespace std;
    
    void myprint(const int &i, char *pmybuf){
        //i并不是mavar的引用,实际是值传递
        //推荐改为const int i
        cout<<i<<endl;
        //指针在detach子线程时,还是指向原来的地址。但是此时地址已经被主线程释放会报错
        cout<< pmybuf <<endl;
    }
    
    int main(){
    
        int mvar=1;
        int &mvary=mvar;
        char mybuf[]="this is a test";
        thread my_thread(myprint, mvar, mybuf);//第一个参数是函数名,后两个参数是函数的参数
    //    my_thread.join();//等待子线程执行结束
        my_thread.detach();
        cout<<"I love China"<<endl;
        return 0;
    }
    

    4.1 解决办法:

    #include <iostream>
    #include <thread>
    #include <string>
    using namespace std;
    
    void myprint(const int i, const string &pmybuf){
        //i并不是mavar的引用,实际是值传递
        //推荐改为const int i
        cout<<i<<endl;
        //指针在detach子线程时,还是指向原来的地址。但是此时地址已经被主线程释放会报错
        cout<< pmybuf <<endl;
    }
    
    int main(){
    
        int mvar=1;
        int &mvary=mvar;
        char mybuf[]="this is a test";
        //如果是隐式转换,会有可能主线程执行完还没进行转换
    //    thread my_thread(myprint, mvar, mybuf);//第一个参数是函数名,后两个参数是函数的参数
        // 因此需要显式的转换,构造临时对象
        thread my_thread(myprint, mvar, string(mybuf));//第一个参数是函数名,后两个参数是函数的参数
    
    //    my_thread.join();//等待子线程执行结束
        my_thread.detach();
        cout<<"I love China"<<endl;
        while(1);
        return 0;
    }
    

    4.2 原因分析

    #include <iostream>
    #include <thread>
    #include <string>
    using namespace std;
    
    class A{
    public:
        int m_i;
        A(int a):m_i(a){cout<<"构造函数执行"<<endl;}
        A(const A &a):m_i(a.m_i){cout<<"拷贝构造函数执行"<<endl;}
        ~A(){cout<<"析构函数调用"<<endl;}
    };
    
    void myprint(const int i, const A &pmybuf){
    
        cout<< &pmybuf <<endl;
    }
    
    int main(){
    
        int mvar=1;
        int mysec=12;
        // 如果是隐式转换,转换过程在子线程中执行,会有可能主线程执行完还没进行转换
        // 因此需要显式的转换,构造临时对象
        // 在线程声明时立刻执行构造
    //    thread my_thread(myprint, mvar, mysec);//第一个参数是函数名,后两个参数是函数的参数
        thread my_thread(myprint, mvar, A(mysec));//第一个参数是函数名,后两个参数是函数的参数
    
    //    my_thread.join();//等待子线程执行结束
        my_thread.detach();
        cout<<"I love China"<<endl;
    
        return 0;
    }
    

    4.3 总结

    1、线程(函数)的传入参数,引用&会失效,指针*还是会传递地址。因为主线程如果销毁了变量内存,子线程的运行就会出错,因此尽量不要在detach()的线程中用传递主线程中的指针
    2、为了防止主线程先结束,detach()的线程还没构造,调用构造的时候要显示的调用类的拷贝构造,即为了防止主线程先结束,只有复制一份内存才行
    3、想要传递真正的引用需要使用std::ref(param_nanm)

    thread thread_obj(func,std::ref(num))
    

    五、传递类对象、智能指针作为线程参数

    5.1 修改子线程中的对象,不会影响主线程中的对象

    #include <iostream>
    #include <thread>
    using namespace std;
    
    class A {
    public:
        mutable int m_i; //mutable关键字,任何情况下都可以修改变量。即使实在const中也可以被修改
        A(int i) :m_i(i) {}
    };
    
    void myPrint(const A& pmybuf)
    {
        pmybuf.m_i = 199;
    
    }
    
    int main()
    {
        A myObj(10);
        //myPrint(const A& pmybuf)中引用不能去掉,如果去掉会多创建一个对象
        //const也不能去掉,去掉会出错
        //即使是传递的const引用,但在子线程中还是会调用拷贝构造函数构造一个新的对象,
        //所以在子线程中修改m_i的值不会影响到主线程
        //如果希望子线程中修改m_i的值影响到主线程,可以用thread myThread(myPrint, std::def(myObj));
        //这样const就是真的引用了,myPrint定义中的const就可以去掉了,类A定义中的mutable也可以去掉了
        //此时拷贝构造也只执行一次了
        thread myThread(myPrint, myObj);
        myThread.join();
        //myThread.detach();
    
        cout << "Hello World!" << endl;
    }
    
    

    5.2 传递智能指针

    #include <iostream>
    #include <thread>
    #include <memory>
    using namespace std;
    
    void myPrint(unique_ptr<int> ptn)
    {
    	cout << "thread = " << std::this_thread::get_id() << endl;
    }
    
    int main()
    {
    	unique_ptr<int> up(new int(10));
    	//独占式指针只能通过std::move()才可以传递给另一个指针
    	//传递后up就指向空,新的ptn指向原来的内存
    	//所以这时就不能用detach了,因为如果主线程先执行完,ptn指向的对象就被释放了
    	thread myThread(myPrint, std::move(up));
    	myThread.join();
    	//myThread.detach();
    
    	return 0;
    }
    
    

    参考链接:

    https://en.cppreference.com/w/cpp/thread/thread/thread

  • 相关阅读:
    android数据恢复
    UVA 690 Pipeline Scheduling
    2017 国庆湖南 Day4
    2017 国庆湖南 Day5
    2017 国庆湖南 Day6
    2017国庆 清北学堂 北京综合强化班 Day1
    2017 国庆湖南Day2
    bzoj 2962 序列操作
    UVA 818 Cutting Chains
    UVA 211 The Domino Effect
  • 原文地址:https://www.cnblogs.com/long5683/p/12995049.html
Copyright © 2011-2022 走看看