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

本文详细探讨了在C++11中使用线程时参数传递的各种情况,包括简单类型、类对象、智能指针和成员函数指针。强调了传递临时对象作为线程参数时的注意事项,如避免引用传递,以及如何确保子线程安全运行。同时,介绍了线程ID的概念,并展示了如何通过构造临时对象在主线程完成前确保其可用。此外,还讨论了使用`std::ref`和`std::move`在多线程环境中的作用,以及智能指针在线程中的使用和管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

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>     
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

森明帮大于黑虎帮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值