C++多线程传参详解

目录

1.线程传参的过程

1.1 内置类型的实参

1.1.1参数按值传递

1.1.2如果想按引用传递,则需要调用std::ref

1.2 类类型的实参

1.2.1 传递的是左值对象

1.2.2 传递的是临时对象(即右值对象)

1.2.3 传递的参数需要隐式类型转换

1.2.4 传递的参数是指针

1.3 传入智能指针unique_ptr


1.线程传参的过程

下面是thread的源代码

template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );

源代码很复杂,反正我是看不懂。但是有一点可以确定,默认情况下实参都是按值传入产生一个副本到thread中(很多人可能都见过这句话,但可能不清楚具体细节,下面举例说明)

      实参从主线程传递到子线程的线程函数中,需要经过两次传递第1次发生在std::thread构造时,实参按值传递并以副本形式被保存到thread的tuple中,这一过程发生在主线程第2次发生在向线程函数传递时,此次传递是由子线程发起即发生在子线程中,并将之前std::thread内部保存的副本以右值的形式(通过调用std::move())传入线程函数

1.1 内置类型的实参

1.1.1参数按值传递

默认情况下,所有参数(含第1个参数可调用对象)均按值以副本的形式保存在std::thread对象中的tuple里。

这一点的实现类似于std::bind(不了解bind的可以去学习一下)

void func(int& a) //左值引用
{
	a = 6;
}

int main()
{
	int b = 1;
	thread t1(func,b); //错误。对实参b按值拷贝产生一个副本,将该副本存放在thread的tuple,
                       //随后对副本 调用std::move,产生一个右值,而func中的参数a是左值
                       //引用,不能绑定到右值
	cout << b << endl;
	t1.join();
	return 0;
}

1.1.2如果想按引用传递,则需要调用std::ref

void func(int& a) //左值引用
{
	a = 6;
}

int main()
{
	int b = 1;
	thread t1(func,std::ref(b); //std::ref传参时,先会创建一个std::ref类型的临时对象,
                           //其中保存着对b的引用。然后这个std::ref再以副本的形式保存在
                         //thread的tuple中。随后这个副本被move到线程函数,由于std::ref重载了
                        //operator T&(),因此会隐式转换为int&类型,因此起到的效果就好象b直接
                          //被按引用传递到线程函数中来

	cout << b << endl;//b的输出为6
	t1.join();
	return 0;
}

1.2 类类型的实参

1.2.1 传递的是左值对象

class A {
private:
	int m_i;
public:
	A(int i) :m_i(i) { cout << "转换构造" <

1.2.2 传递的是临时对象(即右值对象)

class A {
...//定义与前面一样
};

void myPrint2(const A& a) //定义与前面一样
{...}    //4.子线程参数地址是00DED638 30492

int main() {
	int i = 5;
    cout << "主线程id是" <

关于临时对象还有种可能

class A {
...//定义与前面一样
};

void myPrint2(const A& a) //定义与前面一样
{...}   //4.子线程参数地址是00E7D800 28216
 
int main() {
	int i = 5;
   A a(i); //1.转换构造41312      6.析构函数41312
    cout << "主线程id是" <

1.2.3 传递的参数需要隐式类型转换

class A {
...//定义与前面一样
};

void myPrint2(const A& a) //定义与前面一样
{...}    //3.子线程参数地址是00FFF7E4 28552

int main() {
	int i = 5;
    cout << "主线程id是" <

1.2.4 传递的参数是指针

  void func(const string& s)
 { cout <<"子线程id是 " << std::this_thread::get_id() << endl; }

int main(){

  const char* name = "Santa Claus";
  
     thread t(func, &w, name); //ok。首先name在主线程中以const char*类型作为副本被保存
                              //在thread中,当向线程函数func传参时,会先将之前的name副本隐式转 
                               //换为string临时对象再调用move传给func的参数s
                             //同时要注意,这个隐式转换发生在子线程调用时,即在子线程中创建这个临 
                             // 时对象。这就需要确保主线程的生命周期长于子线程,否则name副本就会 
                             /变成野指针,从而无法正确构造出string对象。
    
  

    //std::thread t6(&Widget::func, &w, string(name)); //为避免上述的隐式转换可以带来的bug。可 
                                                      //以在主线程先构造好一个string临时对象, 
                                                      //再传入thread中。这样哪怕调用的是 
                                                     //detach,子线程也很安全

    t.join();  //如果这里改成t.detach,并且如果主线程生命期在这行结束时(意味着主线程在子线程前面 
               //完成运行),就可能发生野指针现象。
}

1.3 传入智能指针unique_ptr

智能指针其实也是个模板类,这里单独拿出来讲一下

void myPrint3(unique_ptr pgn) {cout << myp.get() << endl;}//00E6BEB8

int main() {

 unique_ptr myp(new int(100));
 
   thread mytobj(myPrint3,myp); //错误,首先unique_prt无法进行拷贝,只能移动。而myp是一个
                               //左值,不能对它进行移动构造产生一个副本放入thread
   thread mytobj(myPrint3,std::move(myp));//ok,std::move(myp)返回一个右值,因此调用移动构造产 
                                          //生一个副本放到thread中,这些都发生在主线程
	mytobj.join();

	return 0;
}

再者,讨论一下上述代码在使用detach时的情况。在此之前看下面代码

class B {
private:
	int m_b;
public:
	B(int b) :m_b(b) { cout << "转换构造" << endl; }
	~B() { cout << "析构函数" << endl; }
};

void myPrint3(unique_ptr pgn) { cout << pgn.get() << endl; }

int main() {
	unique_ptr t1(new B(5));
	{
		unique_ptr t2 = std::move(t1);
		cout << "时间点1" << endl;
	}
	cout << "时间点2" << endl;
	return 0;
}

输出结果:
转换构造
时间点1
析构函数
时间点2

这说明t1被销毁时不会调用类B的析构函数,也不会释放分配的堆区内存。因为
t1所含的指针由于后面的move操作已经被置空了。
t2退出作用域时自动销毁,调用类的析构函数,并释放堆区内存

回过头

 void myPrint3(unique_ptr pgn) {cout << myp.get() << endl;}

int main(){
   unique_ptr myp(new int(100));
 
   thread mytobj(myPrint3,std::move(myp)); 
                                         
	mytobj.detach();//即使主线程比子线程先结束,那么myp在销毁时也不会释放堆区内存
                   //此时pgn包含的指针指向那块堆区内存。
                   //那么pgn在C++运行时库中销毁时,会释放堆区内存,不会造成内存泄漏
                   //因此用detach也是安全的
}


你可能感兴趣的:(c++,多线程)