线程传参详解、detach()大坑、成员函数做线程函数

本文详细讨论了在C++中创建线程时如何安全地传递参数,特别是涉及临时对象、引用、线程ID、类对象、智能指针以及成员函数指针。建议避免在detach()时传递指针或引用,以防止内存问题。推荐使用值传递简单类型,使用std::ref传递类对象的引用,以及在需要时使用std::unique_ptr。同时,介绍了成员函数指针作为线程函数的方法,强调了std::ref的重要性以避免不必要的拷贝构造。

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

目录

传递零时对象作为线程参数

小结

线程id

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

成员函数指针做线程函数


传递零时对象作为线程参数

#include <iostream>
#include <thread>

using namespace std;

void myprint(const int& i,char* pmybuf)
{
    printf("&i=%p\n",&i); // i 虽然是引用,但是是值拷贝
    cout<<i<<endl;
    cout<<pmybuf<<endl;  //pmybuf 和主线程的mybuf地址是一样的。
    return ;
}


int main(){

    int mvar=1;
    char mybuf[] = "this is a test!";

    printf("&mvar=%p\n",&mvar);

    thread myobj(myprint,mvar,mybuf);

    //myobj.join();
    myobj.detach();

    cout<<"I Love China!"<<endl;

    return 0;
}

//g++ -g test.cpp -std=c++11 -lpthread -o test.out
//./test.out

  1. 用detach()的时候,参数一定不能传指针,用指针肯定会出问题,因为主线程运行完了之后,对象的内存已经被系统回收了,但是子线程还去使用这个对象。
  2. 用detach()的时候,最好不要传引用,虽然引用是值拷贝的方式进行传递。

#include <iostream>
#include <thread>

using namespace std;


class A
{
    public:
      int m_i;
      A(int a):m_i(a) {cout<<"A::A(int a) constructor is doing..."<<endl;}
      A(const A& a):m_i(a.m_i) {cout<<"A::(A const& a) copy construct is doing..."<<endl;}
      ~A() {cout<<"A::~A() deconstructor is doing..."<<endl;}
};

void myprint(const int& i,const A& pmybuf)
{
    cout<<&pmybuf<<endl;
    return ;
}


int main(){

    int mvar=1;

    //thread myobj(myprint,mvar,A(mvar)); // 这种方式可行,但是会多执行一次拷贝构造函数。
    //即使detach了,子线程会保证安全运行。
    thread myobj(myprint,mvar,mvar);//这种构造方式是不安全的。

    //myobj.join();
    myobj.detach();

    cout<<"I Love China!"<<endl;

    return 0;
}

小结

  1. 如果传递int等简单类型,建议都是值传递,不要用应用,防止节外生枝。
  2. 如果传递类对象,避免隐士类型转化。全部都在创建线程这一行就构建出临时对象来,然后在函数参数里,用引用来接,否则系统还会构造出一次对象出来,避免浪费资源。
  3. 终极结论,建议不适用detach(),只使用join(),这样就不存在局部变量失效导致线程对内存的非法引用问题。除法万不得已,不要使用detach().

二、临时对象作为线程参数继续讲,用测试大法

线程id

1.为什么构造临时对象就安全了?探索根本原因,知其然,而知其所以然。

2.1 线程id,每个线程实际上都对应的是一个数字,不同的线程所对应的数字肯定是不一样的,线程id通过std::this_thread::get_id()来获取。


#include <iostream>
#include <thread>

using namespace std;


class A
{
    public:
      int m_i;
      A(int a):m_i(a) {cout<<"A::A(int a) constructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      A(const A& a):m_i(a.m_i) {cout<<"A::(A const& a) copy construct is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      ~A() {cout<<"A::~A() deconstructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
};

void myprint2(const A&pmybuf)
{
    cout<<"&pmybuf:"<<&pmybuf<<",threadid="<<std::this_thread::get_id()<<endl;
}


int main(){

    int mvar=1;

    //thread myobj(myprint,mvar,A(mvar)); //
    //thread myobj(myprint,mvar,mvar);
    
    cout<<"main thread id is:"<<std::this_thread::get_id()<<endl;
    //thread myobj(myprint2,mvar); 
    thread myobj(myprint2,A(mvar));


    myobj.join();
    //myobj.detach();

    cout<<"I Love China!"<<endl;

    return 0;
}


wj@wj:~/WORKING_DIRECTORY/Learning/cpp_learning$ g++ -g test.cpp -std=c++11 -lpthread -o test.out
wj@wj:~/WORKING_DIRECTORY/Learning/cpp_learning$ ./test.out
main thread id is:140037806438208
A::A(int a) constructor is doing,this=0x7fff3f4d5db8,threadid=140037806438208
A::(A const& a) copy construct is doing,this=0x7fff3f4d5d70,threadid=140037806438208
A::(A const& a) copy construct is doing,this=0x55cb701fc2c8,threadid=140037806438208
A::~A() deconstructor is doing,this=0x7fff3f4d5d70,threadid=140037806438208
A::~A() deconstructor is doing,this=0x7fff3f4d5db8,threadid=140037806438208
&pmybuf:0x55cb701fc2c8,threadid=140037806434048
A::~A() deconstructor is doing,this=0x55cb701fc2c8,threadid=140037806434048
I Love China!

thread myobj(myprint2,mvar);  //这种方式是在子线程中构造A对象,如果用detach(),会出现致命的问题。

thread myobj(myprint2,A(mvar));这种方式是在主线程中构造A对象,如果用detach(),就不会出现致命的问题。

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

std::ref 函数 诞生的原因:

在多线程编程中,主线程和辅线程在进行参数传递的时候,不管是进行值传递,还是引用传递,编译器为了数据安全都是进行拷贝一份。那么这样其实就违背了引用的正常语义了,那么std::ref 函数就诞生了。

#include <iostream>
#include <thread>

using namespace std;


class A
{
    public:
      mutable int m_i;
      A(int a):m_i(a) {cout<<"A::A(int a) constructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      A(const A& a):m_i(a.m_i) {cout<<"A::(A const& a) copy construct is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      ~A() {cout<<"A::~A() deconstructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
};

void myprint2(const A& pmybuf)
{
    pmybuf.m_i=199; //modify m_i,not influence main function
    cout<<"&pmybuf:"<<&pmybuf<<",threadid="<<std::this_thread::get_id()<<endl;
}


int main(){

    A obj(10);

    cout<<"main thread id is:"<<std::this_thread::get_id()<<endl;

    thread mytobj(myprint2,std::ref(obj));

    mytobj.join();

    cout<<"m_i = "<<obj.m_i<<endl;

    cout<<"I Love China!"<<endl;

    return 0;
}


写法二:


#include <iostream>
#include <thread>

using namespace std;


class A
{
    public:
       int m_i;
      A(int a):m_i(a) {cout<<"A::A(int a) constructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      A(const A& a):m_i(a.m_i) {cout<<"A::(A const& a) copy construct is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      ~A() {cout<<"A::~A() deconstructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
};

void myprint2( A& pmybuf) //此处不加const,必须要将成员变量m_i 的mutable去掉
{
    pmybuf.m_i=199; //modify m_i,not influence main function
    cout<<"&pmybuf:"<<&pmybuf<<",threadid="<<std::this_thread::get_id()<<endl;
}


int main(){

    A obj(10);

    cout<<"main thread id is:"<<std::this_thread::get_id()<<endl;

    thread mytobj(myprint2,std::ref(obj));

    mytobj.join();

    cout<<"m_i = "<<obj.m_i<<endl;  // 199

    cout<<"I Love China!"<<endl;

    return 0;
}

用了std::ref 之后,用引用进行参数传递的时候,就符合了引用的本来的语义了。

线程中传递智能指针写法:

#include <iostream>
#include <thread>

using namespace std;


class A
{
    public:
       int m_i;
      A(int a):m_i(a) {cout<<"A::A(int a) constructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      A(const A& a):m_i(a.m_i) {cout<<"A::(A const& a) copy construct is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      ~A() {cout<<"A::~A() deconstructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
};

void myprint2(unique_ptr<int> pzn)
{
    
}


int main(){

    unique_ptr<int> myp(new int(100));
    std::thread mytobj(myprint2,std::move(myp)); 

    mytobj.join(); //此处一定不能用detach(),一定要用join(),因为用detach(),new int(100) 这块内存很可能被系统回收了,那么就不能够转移到 pzn 对象了。


    cout<<"I Love China!"<<endl;

    return 0;
}

如果不用,std::move(myp) 就会编译不过。

std::move(myp) 表示 独占式智能指针,从主函数的myp对象转移到 子线程函数,中的 unique_ptr<int> pzn,pzn对象中。

成员函数指针做线程函数

#include <iostream>
#include <thread>

using namespace std;


class A
{
    public:
       int m_i;
      A(int a):m_i(a) {cout<<"A::A(int a) constructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      A(const A& a):m_i(a.m_i) {cout<<"A::(A const& a) copy construct is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      ~A() {cout<<"A::~A() deconstructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}

      void thread_work(int num)
      {
          cout<<"thread_work() is doing,this="<<this<<","<<"num="<<num<<",this_thread::get_id()::"<<this_thread::get_id()<<endl;
      }
};

void myprint2(unique_ptr<int> pzn)
{
    
}


int main(){

    //unique_ptr<int> myp(new int(100));
    //std::thread mytobj(myprint2,std::move(myp));

    //mytobj.join();

    A myobj(10);
    std::thread mytobj(&A::thread_work,myobj,27);
    //std::thread mytobj(&A::thread_work,std::ref(myobj),27);

    mytobj.join();
    
    cout<<"I Love China!"<<endl;

    return 0;
}

//std::thread mytobj(&A::thread_work,std::ref(myobj),27);  改成这样就不会出现拷贝构造。

//std::thread mytobj(&A::thread_work,&myobj,27); 

std::ref(myobj) 等价写法:&myobj

#include <iostream>
#include <thread>

using namespace std;


class A
{
    public:
       int m_i;
      A(int a):m_i(a) {cout<<"A::A(int a) constructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      A(const A& a):m_i(a.m_i) {cout<<"A::(A const& a) copy construct is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}
      ~A() {cout<<"A::~A() deconstructor is doing,this="<<this<<",threadid="<<this_thread::get_id()<<endl;}

      void thread_work(int num)
      {
          cout<<"thread_work() is doing,this="<<this<<","<<"num="<<num<<",this_thread::get_id()::"<<this_thread::get_id()<<endl;
      }

      void operator() (int num) 
      {
           cout<<"thread_work() is doing,this="<<this<<","<<"num="<<num<<",this_thread::get_id()::"<<this_thread::get_id()<<endl;
      }
};



int main(){

    //unique_ptr<int> myp(new int(100));
    //std::thread mytobj(myprint2,std::move(myp));

    //mytobj.join();

    A myobj(10);
    
    thread mytobj(std::ref(myobj),29); // not call copy construct
    //thread mytobj(&myobj,29); //error 

    mytobj.join();
    
    cout<<"I Love China!"<<endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

repinkply

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

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

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

打赏作者

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

抵扣说明:

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

余额充值