传递临时对象做线程参数
问题一
void myPrint(const int &i, char *buf) {
cout << i << endl;
cout << buf << endl;
}
int main() {
int i = 1;
char mybuf[] = "this is a test!";
//创建线程
thread mythread(myPrint, i, mybuf); //线程的函数传参直接顺序写到后面即可
mythread.join();
cout << "主线程执行结束" << endl;
}
线程参数的传递方法如上:
thread mythread(myPrint, i, buf);
需要传递的线程参数,直接按照顺序写到thread创建线程的后面即可。
如果是thread.join(),主线程等待子线程执行完毕,传参是没有什么大坑的,但是一旦涉及到thread.detach()之后,就要注意传参,因为主线程和子线程分离,主线程执行完毕就会释放资源,诸如i = 1;
、char buf[] = "this is a test!";
等局部变量都会被释放,那么子进程的访问时候会受到影响?
int main() {
int i = 1;
char mybuf[] = "this is a test!";
//创建线程
thread mythread(myPrint, i, mybuf); //线程的函数传参直接顺序写到后面即可
//mythread.join();
mythread.detach(); //使用detach()替换join()
cout << "主线程执行结束" << endl;
}
结论如下:
- 非指针类传参,不会受影响,因为thread创建是拷贝构造,所有局部变量都会被创建一份新的拷贝,主线程释放资源不会影响到子线程的访问。
- 指针类传参,会受影响。因为默认的拷贝构造对于thread是浅拷贝,指针类型直接指向原来的地址,没有开辟新的空间。
解决办法:
- 使用类的仿函数做线程参数,重写拷贝构造函数为深拷贝。
- 将
char*
改为string
代码示例如下:
- 重写深拷贝
class MyPrint {
public:
//有参构造
MyPrint(int i, char *buf) {
m_i = i;
m_buf = new char*(buf);
}
//拷贝构造函数
MyPrint(const MyPrint &p) {
m_i = p.m_i;
m_buf = new char*(*p.m_buf);
}
//析构函数
~MyPrint() {
//对申请的堆进行释放
if (m_buf != NULL) {
delete m_buf;
m_buf = NULL;
}
}
void operator()(){
cout << m_i << endl;
cout << *m_buf << endl;
}
int m_i;
char **m_buf; //注意这里必须用二级指针
};
int main() {
int i = 1;
char mybuf[] = "this is a test!";
MyPrint p(i, mybuf);
//创建线程
thread mythread(p); //线程的函数传参直接顺序写到后面即可
/*mythread.join();*/
mythread.detach();
cout << "主线程执行结束" << endl;
return 0;
}
- 用string代替char
void myPrint(const int &i, const string &buf) { //这里必须用const string *
cout << i << endl;
cout << buf << endl;
}
问题二
对于问题一的解决方案二,使用string替换char *,会带来一个潜藏bug:main中的mybuf(char *)究竟是什么时候转为buf(string)传入子线程的?
事实上,这个时机是不确定的(多线程带来的不确定因素)存在一种可能,主线程main执行完毕,mybuf被回收后,系统才去转string,这就打印错了内容!
同样的对于问题一的解决方案一,也会遇到这个问题,对象的拷贝构造可能会在main结束之前发生,这样main中的mybuf已经被释放,就导致打印出乱码。
解决方法:
在main中,创建一个临时的string,直接先把mybuf转string
void myPrint(const int &i, const string &buf) { //用string代替cahr *
cout << i << endl;
cout << buf << endl;
}
int main() {
int i = 1;
char mybuf[] = "this is a test!";
//创建线程
thread mythread(myPrint, i, string(mybuf)); //必须用string转换一下
/*mythread.join();*/
mythread.detach();
cout << "主线程执行结束" << endl;
return 0;
}
总之:在创建子线程之前,我们必须强制他构造出我们想要的类型,而不是等他隐式构造(mybuf(char *)转buf(string)),所以要用一个string(mybuf)
来传参。
小结:
- 如果传递int这种简单类型,推荐使用值传递,不要用引用
- 如果传递类对象,避免使用隐式类型转换,全部都是创建线程这一行就创建出临时对象,然后在函数参数里,用引用来接,否则还会创建出一个对象
- 终极结论:建议不使用detach(),这样就不会有局部变量失效导致非法引用的问题,这都是因为detach()导致的父子线程分开运行。
线程id
- id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对应的这个数字都不一样
- 线程id可以用C++标准库里的函数来获取。
std::this_thread::get_id();
来获取
thread线程传参理解参考文章:
std::ref
std::reference_wrapper
用于按引用传递对象给 std::bind
或 std::thread
的构造函数.
智能指针
#include <iostream>
#include <thread>
#include <memory>
using namespace std;
void myPrint(unique_ptr<int> ptn)
{
cout << "thread = " << std::this_thread::get_id() << endl;
}
int main()
{
unique_ptr<int> up(new int(10));
thread mythread(myPrint, std::move(up));
mythread.join();
//mythread.detach();
return 0;
}
- 独占式指针只能通过std::move()才可以传递给另一个指针
- 传递后up就指向空,新的ptn指向原来的内存
- 所以这时就不能用detach了,因为如果主线程先执行完,ptn指向的对象就被释放了