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

  • 相关阅读:
    day04用户交互和运算符
    day04垃圾回收机制
    day4
    B2. K for the Price of One (Hard Version)
    C. New Year and Permutation
    Rational Ratio
    C. Two Arrays
    D. MEX maximizing
    B. Infinite Prefixes
    C. Obtain The String
  • 原文地址:https://www.cnblogs.com/long5683/p/12995049.html
Copyright © 2011-2022 走看看