C++11多线程第三篇:线程传参详解,detach()大坑,成员函数做线程参数

Posted 森明帮大于黑虎帮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11多线程第三篇:线程传参详解,detach()大坑,成员函数做线程参数相关的知识,希望对你有一定的参考价值。


文章目录

3.1 传递临时对象作为线程参数

3.1.1 要避免的陷阱(解释1)

代码如下:

#include<iostream>
#include<thread>
#include<iostream>
void MyPrint(int& val,std::string& str)
    std::cout<<val<<std::endl;
    std::cout<<str.c_str()<<std::endl;

void myPrint(const int &m_val, std::string& str)

	//如果线程从主线程detach()了
	//m_val不是var真正的引用,实际上值传递,即使主线程运行完毕了,子线程用i仍然是安全的,但仍不推荐传递引用
	//推荐改为const int i
	std::cout << m_val << std::endl;
	//pmybuf还是指向原来的字符串,所以这么写是不安全的
	std::cout << str.c_str() << std::endl;


int main()
    int val=10;
    //nt& m_val=val;
    //char arr[]="more thread";
    std::string arr="more thread";
    std::thread obj(myPrint,val,arr);  //第一个参数传可执行对象,线程入口函数,后面传线程入口函数参数
    if(obj.joinable())
        obj.join();
        //obj.detach();
    
    std::cout<<"main thread end"<<std::endl;
    return 0;

输出结果:

10
more thread
main thread end
PS C:\\Users\\Administrator\\Desktop\\thread\\build\\thread可执行对象传参陷阱\\Debug>

std::thread obj(myPrint,val,arr); //第一个参数传可执行对象,线程入口函数,后面传线程入口函数参数

  • 线程入口函数以及线程入口函数的参数实现时参数如果采用的是引用接收的话而不是真正的引用,也是值传递。所以当使用detach()主线程与子线程分开执行的时候也不影响,因为不是引用传递而是值传递,可以调试根据传递的参数地址对比就可以得出来。但推荐这里线程入口函数参数一般不加&。
  • 线程入口函数以及线程入口函数的参数实现时参数如果采用的是指针接收传递过来的数组或者对象的话这样是不可取的,因为指针指向的是数组的地址或者是对象的地址,指针地址和主线程中的参数地址一样。当采用的线程函数是detach()时候这是就会因为主线程先于子线程退出,那么主线程中的成员变量是局部变量那么被销毁的传参就会发生异常。

3.1.2 要避免的陷阱(解释2)

  • 只要用临时构造的A类对象作为线程入口函数参数的第二个参数传递给线程,那么一定可以在主线程运行完毕之前把线程入口函数的第二个参数构建出来,从而确保detach()子线程安全运行,不发生异常。

代码如下:

#include<iostream>
#include<thread>


class A
public:
    A(const int& a)
    :a_(a)
        std::cout<<"A的构造函数 "<<this<<std::endl;
    
    A(const A& aa)
    :a_(aa.a_)
        std::cout<<"A的拷贝构造 "<<this<<std::endl;
    
    ~A()
        std::cout<<"A的析构函数 "<<this<<std::endl;
    
private:
    int a_;
;
void MyPrint(const int i,const A& str)
    std::cout<<i<<std::endl;
    std::cout<<&str<<std::endl;


int main()
    int val=10;
    //std::string str="more thread";
    int second=100;
    //std::thread obj(MyPrint,val,second);  //second作为线程入口函数实参构造转换为A类型对象的第二个参数
    std::thread obj(MyPrint,val,A(second));  //second作为线程入口函数实参构造转换为A类型对象的第二个参数
    if(obj.joinable())
        obj.join();
        //obj.detach();
    
    std::cout<<"hhhh"<<std::endl;
    return 0;

输出结果:

A的拷贝构造
A的析构函数
10
0000025EB5E71B50
A的析构函数
hhhh
PS C:\\Users\\Administrator\\Desktop\\thread\\build\\thread可执行对象传参陷阱2\\Debug>

3.1.3 总结

  • 若传递int这种简单类型参数,建议都使用值传递,不要引用传递。防止节外生枝。

  • 如果传递的是类对象,避免隐式类型转换。全部都在创建线程这一行就构建出临时对象来,然后在函数参数里,用引用来接收对象,防止再一次发生拷贝构造影响内存效率。

  • 终极结论:

    • 建议不适用detach(),只使用join(),这样就不存在局部变量失效导致线程对内存的非法引用问题。

3.2 临时对象作为线程参数进一步详解

3.2.1 线程id概念

  • id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对于的数字都不一样,也就是说,不同的线程,它的线程id(数字)必然是不同的。
  • 线程id可以是用C++标准库里的函数来获取。std::this_thread::get_id()来获取的。

3.2.2 临时对象构造时机抓捕

代码如下:

#include<iostream>
#include<thread>

class A
public:
    A(int a)
    :a_(a)
        std::cout<<"A的构造函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;
    
    A(const A& a)
    :a_(a.a_)
        std::cout<<"A的拷贝函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;        
    
    ~A()
        std::cout<<"A的析构函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;       
    
private:
    int a_;
;

// void MyPrint(const int& val)
void MyPrint(const A& val)
    std::cout<<"子线程MyPrint函数的参数地址: "<<&val<<" 线程id:"<<std::this_thread::get_id()<<std::endl;

int main()
    int val=10;
    //std::thread obj(MyPrint,val);
    std::thread obj(MyPrint,A(val));
    if(obj.joinable())
        obj.join();
    
    std::cout<<"主线程main函数的线程id:"<<std::this_thread::get_id()<<std::endl;
    return 0;

输出结果:

PS D:\\C++11多线程代码编写\\build\\thread临时对象构造时机抓捕\\Debug> .\\main.exe
A的构造函数: 10 this指针地址:000000C378EFF984 主线程id:2100
A的拷贝函数: 10 this指针地址:0000022C4ECA3280 主线程id:2100
A的析构函数: 10 this指针地址:000000C378EFF984 主线程id:2100
子线程MyPrint函数的参数地址: 0000022C4ECA3280 线程id:13520
A的析构函数: 10 this指针地址:0000022C4ECA3280 主线程id:13520
主线程main函数的线程id:2100
PS D:\\C++11多线程代码编写\\build\\thread临时对象构造时机抓捕\\Debug>

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

3.3.1 传递类对象作为线程参数

  • mutable修饰成员变量或者成员函数,表示成员变量和成员函数参数可以修改。

代码如下:

#include<iostream>
#include<thread>

class A
public:
    A(int a)
    :a_(a)
        std::cout<<"A的构造函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;
    
    A(const A& a)
    :a_(a.a_)
        std::cout<<"A的拷贝函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;        
    
    ~A()
        std::cout<<"A的析构函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;       
    
// private:
    // int a_;
    mutable int a_; //mutable表示变量可以修改,const也可以
;

// void MyPrint(const int& val)
void MyPrint(const A& val)
    val.a_=100;
    std::cout<<"&val.a_的地址:"<<&val.a_<<std::endl;
    std::cout<<"子线程MyPrint函数的参数地址: "<<&val<<" 线程id:"<<std::this_thread::get_id()<<std::endl;

int main()
    //int val=10;
    //std::thread obj(MyPrint,val);
    A a(10);
    std::thread obj(MyPrint,a);
    if(obj.joinable())
        obj.join();
    
    std::cout<<"主线程main函数的线程id:"<<std::this_thread::get_id()<<std::endl;
    return 0;

输出结果:

A的构造函数: 10 this指针地址:000000F448EFFD84 主线程id:11712
A的拷贝函数: 10 this指针地址:0000024FE7102BC0 主线程id:11712
&val.a_的地址:0000024FE7102BC0
子线程MyPrint函数的参数地址: 0000024FE7102BC0 线程id:8836
A的析构函数: 100 this指针地址:0000024FE7102BC0 主线程id:8836
主线程main函数的线程id:11712
A的析构函数: 10 this指针地址:000000F448EFFD84 主线程id:11712
PS D:\\C++11多线程代码编写\\build\\thread传递类对象、智能指针作为线程参数\\Debug>
  • 如上结果所示:变量的值并没有改变,因为两个变量的地址不一样。如果要想改变的话加上std::ref。
  • 如果希望子线程中修改m_i的值影响到主线程,可以用thread myThread(MyPrint, std::ref(myObj))。
  • 这样const就是真的引用了,MyPrint定义中的const就可以去掉了,类A定义中的mutable也可以去掉了。

代码如下:

#include<iostream>
#include<thread>

class A
public:
    A(int a)
    :a_(a)
        std::cout<<"A的构造函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;
    
    A(const A& a)
    :a_(a.a_)
        std::cout<<"A的拷贝函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;        
    
    ~A()
        std::cout<<"A的析构函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;       
    
// private:
    // int a_;
    // mutable int a_; //mutable表示变量可以修改,const也可以
    int a_;
;

// void MyPrint(const int& val)
void MyPrint(A& val)
    val.a_=100;
    std::cout<<"&val.a_的地址:"<<&val.a_<<std::endl;
    std::cout<<"子线程MyPrint函数的参数地址: "<<&val<<" 线程id:"<<std::this_thread::get_id()<<std::endl;

int main()
    //int val=10;
    //std::thread obj(MyPrint,val);
    A a(10);
    // std::thread obj(MyPrint,a);
    std::thread obj(MyPrint,std::ref(a));
    if(obj.joinable())
        obj.join();
    
    std::cout<<"主线程main函数的线程id:"<<std::this_thread::get_id()<<std::endl;
    return 0;

输出结果:

PS D:\\C++11多线程代码编写\\build\\thread传递类对象、智能指针作为线程参数\\Debug> .\\main.exe 
A的构造函数: 10 this指针地址:0000002019B9F774 主线程id:11044
&val.a_的地址:0000002019B9F774
子线程MyPrint函数的参数地址: 0000002019B9F774 线程id:7904
主线程main函数的线程id:11044
A的析构函数: 100 this指针地址:0000002019B9F774 主线程id:11044
PS D:\\C++11多线程代码编写\\build\\thread传递类对象、智能指针作为线程参数\\Debug>
  • 如结果所示:因为子线程中的变量地址和主线程一样,所以可以修改。

3.3.2 传递智能指针作为线程参数

  • 独占式指针只能通过std::move()才可以传递给另一个指针。
  • 所以这时就不能用detach了,因为如果主线程先执行完,sp指向的对象就被释放了。

代码如下:

#include<iostream>
#include<thread>

class A
public:
    A(int a)
    :a_(a)
        std::cout<<"A的构造函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;
    
    A(const A& a)
    :a_(a.a_)
        std::cout<<"A的拷贝函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;        
    
    ~A()
        std::cout<<"A的析构函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;       
    
// private:
    // int a_;
    // mutable int a_; //mutable表示变量可以修改,const也可以
    int a_;
;

// void MyPrint(const int& val)
// void MyPrint(A& val)
//     val.a_=100;
//     std::cout<<"&val.a_的地址:"<<&val.a_<<std::endl;
//     std::cout<<"子线程MyPrint函数的参数地址: "<<&val<<" 线程id:"<<std::this_thread::get_id()<<std::endl;
// 
void MyPrint(std::shared_ptr<int> spa)
    std::cout<<*spa<<std::endl;
    std::cout<<"子线程MyPrint函数的参数地址: "<<&spa<<" 线程id:"<<std::this_thread::get_id()<<std::endl;


int main()
    //int val=10;
    //std::thread obj(MyPrint,val);
    // A a(10);
    // std::thread obj(MyPrint,a);
    std::shared_ptr<int> sp(new int(10));
    // std::thread obj(MyPrint,std::ref(a));
    //std::thread obj(MyPrint,sp);
    std::cout<<std::endl;
    std::cout<<&sp<<std::endl;
    std::thread obj(MyPrint,std::move(sp));
    if(obj.joinable())
        obj.join();
    
    std::cout<<&sp<<std::endl;
    std::cout<<"主线程main函数的线程id:"<<std::this_thread::get_id()<<std::endl;
    return 0;

输出结果:

0000004D7EAFF608
10
子线程MyPrint函数的参数地址: 0000004D7EEFF8A0 线程id:11876
0000004D7EAFF608
主线程main函数的线程id:15428
PS D:\\C++11多线程代码编写\\build\\thread传递类对象、智能指针作为线程参数\\Debug>

3.4 用类成员函数指针做线程参数

代码如下:

#include<iostream>
#include<thread>

class A
public:
    A(int a)
    :a_(a)
        std::cout<<"A的构造函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;
    
    A(const A& a)
    :a_(a.a_)
        std::cout<<"A的拷贝函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;        
    
    ~A()
        std::cout<<"A的析构函数: "<<a_<<" this指针地址:"<<this<<" 主线程id:"<<std::this_thread::get_id()<<std::endl;       
    
    void Thread_Work(int num)
        std::cout<<"子线程成员函数执行"<<" this指针地址:"<<this<<" 子线程id:"<<std::this_thread::get_id()<<std::endl;       
    
private:
    // int a_;
    // mutable int a_; //mutable表示变量可以修改,const也可以
    int a_;
;

// void MyPrint(const int& val)
// void MyPrint(A& val)
//     val.a_=100;
//     std::cout<<"&val.a_的地址:"<<&val.a_<<std::endl;
//     std::cout<<"子线程MyPrint函数的参数地址: "<<&val<<" 线程id:"<<std::this_thread::get_id()<<std::endl;
// 
void MyPrint(std::shared_ptr<int> spa)
    std::cout<<&spa<<std::endl;
    std::cout<<*spa<<std::endl;
    std::cout<<"子线程MyPrint函数的参数地址: "<<&spa<<" 线程id:"<<std::this_thread::get_id()<<std::endl;


int main()
    //int val=10;
    //std::thread obj(MyPrint,val);
    // A a(10);
    // std::thread obj(MyPrint,a);
    // std::shared_ptr<int> sp(new int(10));
    // std::thread obj(MyPrint,std::ref(a));
    //std::thread obj(MyPrint,sp);
    // std::cout<<std::endl;
    // std::cout<<&sp<<std::endl;
    // std::thread obj(MyPrint,std::move(sp));
    A a(10);
    std::thread obj(&A::Thread_Work,a,100);
    if(obj.joinable())
        obj.join();
    
    //std::cout<<&sp<<std::endl;
    std::cout<<"主线程main函数的线程id:"<<std::this_thread::get_id()<<std::endl;
    return 0;

输出结果:

PS D:\\C++11多线程代码编写\\build\\thread用成员函数指针做线程参数\\Debug> .\\main.exe
A的构造函数: 10 this指针地址:000000617A10F734 主线程id:8504
A的拷贝函数: 10 this指针地址:000001C09F7D05E4 主线程id:8504
子线程成员函数执行 this指针地址:000001C09F7D05E4 子线程id:15448
A的析构函数: 10 this指针地址:000001C09F7D05E4 主线程id:15448
主线程main函数的线程id:8504
A的析构函数: 10 this指针地址:000000617A10F734 主线程id:8504
PS D:\\C++11多线程代码编写\\build\\thread用成员函数指针做线程参数\\Debug>     

以上是关于C++11多线程第三篇:线程传参详解,detach()大坑,成员函数做线程参数的主要内容,如果未能解决你的问题,请参考以下文章

C++11多线程 多线程传参详解

课时4 线程传参详解,detach()大坑,成员函数做线程函数

C++11多线程中的detach()join()joinable()

C++11多线程,线程对象(thread对象)joinable()join()detach()左值智能指针

C++11多线程,线程对象(thread对象)joinable()join()detach()左值智能指针

C++11多线程运行报错:terminate called without an active exception(没有join或detach子线程)