• 三、线程传参


    线程参数

    引用和指针:

    陷进一:不要用引用和指针,因为地址可以被主线程运行完后释放了。

    陷阱二:将对象传递给子线程时,可能主线程都运行完了,对象还没构建。

    • 通过源码可知,thread类禁用了拷贝构造函数,但支持对象move
    • 带参架构函数接受的值是右值引用。非带参和move的线程都是非joinable的。
    • 指针变量如果用detach,很可能主线程结束了,子线程读取指针变量发生错误。
    • 都是右值引用,为什么int安全,int*类型不安全:
      • 主线程中的i=1和int *p在传参时应该都是传的临时值。
      • int* && pp=p后,其实最终访问的数据还是i,如果 i 释放了,那么p的副本pp也会访问失败。
      • int& i;由于子线程不接受左值,主线程会根据左值生成临时值,然后右值引用=临时值。

     临时变量传参:

      右值引用就是对临时对象的引用,右值引用在临时对象释放后,还是能更改右值引用所引用的值。下面的代码就是主线程负责申请临时空间,然后通过右值引用拿来用。

      string& pmybuf=string(mybuf):

      通过上面分析,首先得到 pmybuf 的副临时对象,然后把 pmybuf 副本转换为右值引用。就类似函数参数要求int ,但传过来的是double。

     1 void myprint(const int &i, char *pmybuf) //分析发现,i并不是myvar的引用,实际是值传 
     2                                          //递,那么我们认为,即便主线程detach了子线 
     3                                          //程,那么子线程中用i值仍然是安全的
     4 {
     5     cout << i << endl;
     6     cout << pmybuf << endl;         //指针在detach子线程时,绝对会有问题
     7 }
     8  
     9 int main() {
    10     int myvar = 1;
    11     int &mvary = myvar;
    12     char mybuf[] = "goog luck";
    13     thread myobj(myprint, myvar, mybuf); 
    14     thread myobj(myprint, myvar, string(mybuf)); //这种写法是安全的,第一行改成const string& pmybuf
    15     myobj.join();
    16     cout << "end" << endl;
    17     return 0;
    18 }

    线程ID

    id是个数字,每个线程实际上都对应着一个数字,而且每个线程对应的数字都不同,也就是说不同的线程id必然是不同的;

    线程id可以通过C++标准库里的函数来获取。std::this_thread::get_id().

    thread类直接包装了一个pthread_t,在linux下实际是unsigned long int。

    class  thread
    {
        pthread_t __t_;
        id get_id() const _NOEXCEPT {return __t_;}
    }

    用了一个std::unique_ptr来包装用户定义的线程函数:

    拒绝隐式转换

    如果thread()中的参数不是线程函数需要的类对象,

    class A {
    public:
        int m_i;
        A(int a) :m_i(a) {
            cout << "A::A(int a)构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
        }
        A(const A &tmpa) :m_i(tmpa.m_i) {
            cout << "A::A(int a)拷贝构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
        }
     
        ~A() {
            cout << "A::A(int a)析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
        }
    };
     
    void myprint2(const A &mybuf) { //这里建议加const
        cout << "子线程的参数地址" << &mybuf << std::this_thread::get_id() << endl;
    }
     
    int main() {
        cout << "主线程id" << std::this_thread::get_id() << endl;
        int var = 1;
        std::thread myobj(myprint2, var);
        myobj.join();
    }

    运行结果:

     由此可知,类型转换是在在线程中进行的(之前的分析也推出这个结论),那么久带来了一个问题:

      主线程已经吧var的临时对象给子线程了,但是子线程还没构造A对象(比如时间片还没轮到子线程),主线程久执行完了,那临时对象也肯定会销毁,这种情况就会导致程序异常。

      为什么不让子线程生成临时对象呢:也就是可能子线程的时间片还没到,没机会生成临时对象。(其实是我猜的)

    修改代码:

    int main() {
        cout << "主线程id" << std::this_thread::get_id() << endl;
        int var = 1;
        std::thread myobj(myprint2, A(var));
        myobj.join();
    }

    主线程:

       构造函数:将var转换成A类;拷贝构造:根据A生成A的临时对象;

    子线程:

      const A& a=临时A对象后:const A& 可以接受左值引用也可以接受右值引用

     如果参数不用const A&,会调用两次A的拷贝构造函数,子线程会多调用一次拷贝构造函数。A a=临时A对象。

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

    void myprint2(const A &mybuf) { //这里建议加const
        mybuf.m_i = 10;
        cout << "子线程的参数地址" << &mybuf << std::this_thread::get_id() << endl;
    }

    上面代码虽然传递的参数是引用,但是当它在子线程中执行时并不会影响主线程的变量。也就是说mybuf仍然是拷贝出来的,不是主线程传递对象的引用。

    void myprint2(const A &mybuf) { //这里建议加const
        cout << "子线程的参数地址" << &mybuf << std::this_thread::get_id() << endl;
    }
     
    int main() {
        cout << "主线程id" << std::this_thread::get_id() << endl;
        A obj(1);
        std::thread mythread(myprint2, std::ref(obj));
        myobj.join();
    }

    当我们使用了std::ref(),可以看到,就可以把主线程的参数传递到子线程中,不会再构造对象副本。 

     

     智能指针作为参数传递

    void myprint2(const unique_ptr<A> &mybuf) {
        mybuf->m_i = 4;
        cout << "子线程的参数地址" << &mybuf << std::this_thread::get_id() << endl;
    }
     
    int main() {
        cout << "主线程id" << std::this_thread::get_id() << endl;
        auto a = make_unique<A>(1);
        std::thread myobj(myprint2, std::move(a));
        myobj.join();
    }

    用成员函数指针作为线程函数

    class A {
    public:
        mutable int m_i;
        A(int a) :m_i(a) {
            cout << "A::A(int a)构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
        }
        A(const A &tmpa) :m_i(tmpa.m_i) {
            cout << "A::A(int a)拷贝构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
        }
     
        ~A() {
            cout << "A::A(int a)析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
        }
     
        void thread_work(int num){
            cout << "子线程thread_work启动" << endl;
        }
    };
     
     
    int main() {
        cout << "主线程id" << std::this_thread::get_id() << endl;
        int var = 10;
        A a(1);  //生成一个类对象
        std::thread myobj(&A::thread_work, a, var);  //注意:第二个参数是一个类对象
        myobj.join();
        return 0;
    }

     在主线程中又执行了拷贝构造函数

    int main() {
        cout << "主线程id" << std::this_thread::get_id() << endl;
        int var = 10;
        A a(1);  //生成一个类对象
            //std::thread myobj(&A::thread_work, &a, var);
        std::thread myobj(&A::thread_work, std::ref(a), var);  //注意:第二个参数是一个类对象
        myobj.join();
        return 0;
    }

    增加std::ref()后 

     用可调用对象作为线程函数

    class A {
    public:
        mutable int m_i;
        A(int a) :m_i(a) {
            cout << "A::A(int a)构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
        }
        A(const A &tmpa) :m_i(tmpa.m_i) {
            cout << "A::A(int a)拷贝构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
        }
     
        ~A() {
            cout << "A::A(int a)析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
        }
     
        void thread_work(int num){
            cout << "子线程thread_work启动" << endl;
        }
     
        void operator()(int num) {
            cout << "子线程()执行" << endl;
        }
    };
     
    int main() {
        cout << "主线程id" << std::this_thread::get_id() << endl;
        int var = 10;
        A a(1);  //生成一个类对象
        std::thread myobj(a, var);
        //std::thread myobj(std::ref(a), var); 将不会再调用拷贝构造函数而是把本身作为线 
                                             //程函数
        myobj.join();
        return 0;
    }

    心之所愿,永不相忘
  • 相关阅读:
    FarPoint FpSpread控件的使用收藏
    在Oracle中使用Guid
    oracle 语句的妙用例子
    让服务器iis支持.apk文件下载的设置方法
    oracle 自动生存清库脚本
    winform 消息通讯组件实习
    在css中使用边框做三角形
    JavaScript闭包和ajax
    JavaScript面向对象
    正则表达式
  • 原文地址:https://www.cnblogs.com/zgll/p/15288034.html
Copyright © 2020-2023  润新知