线程传参坑点总结

最近工作当中对于在c++11多线程的使用过程当中总有许多的疑惑,且遇到了很多的坑,今天特意进行总结一番。
一:传递临时对象作为线程参数
范例一:

#include <iostream>
#include <thread>
using namespace std;
void func(int& i, char* buf)
{
	printf("&i = %d\n", &i);
	printf("buf = %p\n", buf);
	cout << buf << endl;
}
int main()
{
	int var = 1;
	int& mvar = var;
	printf("&var = %d\n", &var);
	printf("&mvar = %d\n", &mvar);
	char mbuf[] = "abucdf";
	printf("&mbuf = %p\n", mbuf);
	std::thread mthread(func, std::ref(var), mbuf);
	mthread.join();
	//mthread.detach();
}

在这里插入图片描述

从输出结果可以看出,子线程当中的i正是绑定的主线程当中的var,同理,buf指向的内存与mbuf也是一样的,如果这里不是用join而是使用detach会带来很多的隐患问题,如果主线程已经执行完毕,那么变量var不就被系统回收了吗?那么我们在子线程当中使用i,就会存在隐患。所以,尽量不要在线程传参当中使用指针与引用。我们针对上面的范例进行一下优化

范例二:

#include <iostream>
#include <thread>
using namespace std;

void func(const int& i, const string& buf)
{
	printf("&i = %d\n", &i);
	printf("buf = %p\n", buf);
	cout << buf << endl;
}
int main()
{
	int var = 1;
	int& mvar = var;
	printf("&var = %d\n", &var);
	printf("&mvar = %d\n", &mvar);
	char mbuf[] = "abucdf";
	printf("&mbuf = %p\n", mbuf);
	std::thread mthread(func, var, mbuf);
	mthread.join();
	//mthread.detach();
}

在这里插入图片描述
从输出结果来看子线程使用的两个参数是重新拷贝了一份进去,这样就不必担心范例一存在的问题了。
这里的话,随便提一下,就是尽量使用const引用,对于thread来说,这样才会产生临时对象,而如果不用const好像会编译错误,当然,真正的引用要使用std::ref才行。

看到这里我们貌似觉得没有问题了?实际上,还存在一个问题,上面范例二当中是希望mbuf转化为string,那么这个转换是在什么时候,或者说在那里进行的?如果主线程都执行完毕了,mbuf都回收了,那么这个时候在转换还有什么意义?

我们再针对范例二进行优化

范例三:

#include <iostream>
#include <thread>
using namespace std;
void func(const int& i, const string& buf)
{
	printf("&i = %d\n", &i);
	printf("buf = %p\n", buf);
	cout << buf << endl;
}

int main()
{
	int var = 1;
	int& mvar = var;
	printf("&var = %d\n", &var);
	printf("&mvar = %d\n", &mvar);
	char mbuf[] = "abucdf";
	printf("&mbuf = %p\n", mbuf);
	std::thread mthread(func, var, string(mbuf));
	mthread.join();
	//mthread.detach();
}

在这里插入图片描述
我们看到范例三用string(mbuf)这个生成了一个临时对象,那么这种方式对于使用detach来说是否可以避免隐患问题?我们继续探讨:
范例四:

#include <iostream>
#include <thread>
using namespace std;

class A
{
public:
	A(int a) :m_i(a)
	{
		std::cout << "构造函数执行了" << this<<std::endl;
	}
	A(const A& a)
	{
		std::cout << "拷贝构造函数执行了" << this <<std::endl;
	}
	~A()
	{
		std::cout << "析构函数执行了" << std::endl;
	}
	int m_i;
};

void func(int i, const A& buf)
{
	std::cout << i << std::endl;
	std::cout << &buf << std::endl;
}

int main()
{
	int a = 1;
	int senvar = 2;
	std::thread mthread(func, a, senvar);
	mthread.join();
	//mthread.detach();
}

在这里插入图片描述
通过结果可以知道,子线程当中的buf确实是通过senvar构造的,但是如果换成detach会怎么样?
可能一句语句也不会输出了,本来是希望用一个senvar变量去构造一个临时对象的,然后给子线程使用,但是现在还没构造出来这个变量,主线程就执行完毕了。。
我们换一种方式进行研究。

#include <iostream>
#include <thread>
using namespace std;


class A
{
public:
	A(int a) :m_i(a)
	{
		std::cout << "构造函数执行了" << this<<std::endl;
	}
	A(const A& a)
	{
		std::cout << "拷贝构造函数执行了" << this <<std::endl;
	}
	~A()
	{
		std::cout << "析构函数执行了" << this <<std::endl;
	}
	int m_i;
};

void func(int i, const A& buf)
{
	std::cout << i << std::endl;
	std::cout << &buf << std::endl;
}

int main()
{
	int a = 1;
	int senvar = 2;
	std::thread mthread(func, a, A(senvar));
	//mthread.join();
	mthread.detach();
}

在这里插入图片描述
使用了A(senvar)以后我们就能确保在主线程当中就把这个临时对象构造出来了,这样主线程即使执行完毕,也不会影响子线程的使用。至于到底什么时候进行的构造,这个可以把线程id打印出来观察。

总结:所以对于像一些简单的类型,比如int,尽量用值传递,如果使用类对象作为传递参数,也要尽量避免发生隐式类型转换,尽量在创建线程这一行代码就把临时对象构造出来,而线程入口函数要尽量使用const引用做为形参,这个与thread内部原理有关,如果不用引用可能会多调用几次拷贝构造函数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值