17.3 线程传参详解、detach坑与成员函数作为线程函数

一:传递临时对象作为线程参数

<1>要避免的陷阱(解释1)

void myprint(const int& i, char* pmybuf)
{
	cout << i << endl;  //分析认为(查看i的内存地址),i并不是mavary的引用,实际是值传递,即便主线程detach了子线程,那么子线程中用这个i值仍然是安全的。
	cout << pmybuf << endl;  //通过查看pmybuf的地址,指针在detach子线程时,绝对会有问题
	return;
}

int main()
{
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is test!";
	thread mytobj(myprint, mvar, mybuf);
	//mytobj.join();
	mytobj.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}

这是有问题的。

//void myprint(const int& i, char* pmybuf)
//void myprint(const int i, char* pmybuf)
void myprint(const int& i, const string& pmybuf)
{
	cout << i << endl;  //分析认为(查看i的内存地址),i并不是mavary的引用,实际是值传递,即便主线程detach了子线程,那么子线程中用这个i值仍然是安全的。
	cout << pmybuf.c_str() << endl;  //查看pmybuf内存地址,与main函数中,mybuf内存地址不一致,感觉是线程安全了。
	return;
}

int main()
{
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is test!";
	thread mytobj(myprint, mvar, mybuf);  //但是mybuf到底是在什么时候转换成string
	//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string的可能性;
	//mytobj.join();
	mytobj.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}

这个char转string也是有问题的。

//void myprint(const int& i, char* pmybuf)
//void myprint(const int i, char* pmybuf)
void myprint(const int& i, const string& pmybuf)
{
	cout << i << endl;  //分析认为(查看i的内存地址),i并不是mavary的引用,实际是值传递,即便主线程detach了子线程,那么子线程中用这个i值仍然是安全的。
	cout << pmybuf.c_str() << endl;  //查看pmybuf内存地址,与main函数中,mybuf内存地址不一致,感觉是线程安全了。
	return;
}

int main()
{
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is test!";
	//thread mytobj(myprint, mvar, mybuf);  //但是mybuf到底是在什么时候转换成string
	//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string的可能性;
	thread mytobj(myprint, mvar, string(mybuf));  //我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中肯定有效的对象。
	//mytobj.join();
	mytobj.detach();  //子线程和主线程分别执行。
	cout << "main主函数执行结束" << endl;
	return 0;
}

这是线程安全的。

<2>要避免的陷阱(解释2)

class A
{
public:
	int m_i;
	//mutable int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象。
	A(int a) :m_i(a)
	{
		cout << "[A::A(int a)构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	A(const A& a) :m_i(a.m_i)
	{
		cout << "[A::A(cosnt A)]拷贝构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	~A()
	{
		cout << "[A::~A()析构函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void thread_work(int num)
	{
		cout << "子线程thread_work执行了" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void operator()(int num)
	{
		cout << "子线程()执行 " << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
};

void myprint(const int i, const A& pmybuf)
{
	cout << "&pmybuf的地址: " << &pmybuf << endl;  //这里打印的是pmybuf对象的地址
	return;
}

int main()
{
	int var = 1;
	int secondvar = 12;
	thread mytobj(myprint, var, A(secondvar));  //我们希望secondvar转成A类型对象传递给myprint的第二个参数
	//thread mytobj(myprint, var, static_cast<A>(secondvar));
	//在创建线程的同时构造临时对象的方法传递参数是可行的
	//mytobj.join();
	mytobj.detach();
	//cout << "main主函数执行结束" << endl;
	return 0;
}

事实1:只要用临时构造的类A对象作为参数传递给线程,那么就一定能够在主线程执行完毕前把线程的第二个参数构建出来,从而确保即便detach()子线程也安全运行。

class A
{
public:
	int m_i;
	//mutable int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象。
	A(int a) :m_i(a)
	{
		cout << "[A::A(int a)构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	A(const A& a) :m_i(a.m_i)
	{
		cout << "[A::A(cosnt A)]拷贝构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	~A()
	{
		cout << "[A::~A()析构函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void thread_work(int num)
	{
		cout << "子线程thread_work执行了" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void operator()(int num)
	{
		cout << "子线程()执行 " << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
};

void myprint(const int i, const A& pmybuf)
{
	cout << "&pmybuf的地址: " << &pmybuf << endl;  //这里打印的是pmybuf对象的地址
	return;
}

void myprint2(A& pmybuf)
{
	pmybuf.m_i = 199;  //我们修改该值不会影响到main函数
	cout << "子线程的参数地址是" << &pmybuf << "  " << "this id = " << this_thread::get_id() << endl;
}

void myprint3(unique_ptr<int> p)
{

}

void printPA(const int i, const A& a)
{
	cout << &a << endl;
}

int main()
{
	int var = 1;
	int secondvar = 12;
	thread mytobj(myprint, var, A(secondvar));  //我们希望secondvar转成A类型对象传递给myprint的第二个参数
	//thread mytobj(myprint, var, static_cast<A>(secondvar));
	//在创建线程的同时构造临时对象的方法传递参数是可行的
	//mytobj.join();
	mytobj.detach();
	//cout << "main主函数执行结束" << endl;
	return 0;
}

A(int a) :m_i(a)构造函数执行00000047BC6FFB64
A(const A& a)拷贝构造函数执行000001ADA3B51F80
~A()析构函数执行00000047BC6FFB64
main主函数执行结束

ps:
发现void myprint(const int& i, const A& pmybuf),
A对象是否是引用,都会构建拷贝构造函数。书上说是由于thread引起的。暂时不解。
若A对象没有引用,则会调用两次拷贝构造函数,第二次调用感觉有问题。

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

二:临时对象作为线程参数继续讲

常用测试大法
<1>线程id概念
id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对应的数字都不同;也就是说不同的线程,它的线程id(数字)必然是不同;
线程id可以用C++标准库里的函数来获取,std::this_thread::get_id()来获取;

<2>临时对象构造时机捕获

class A
{
public:
	int m_i;
	//mutable int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象。
	A(int a) :m_i(a)
	{
		cout << "[A::A(int a)构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	A(const A& a) :m_i(a.m_i)
	{
		cout << "[A::A(cosnt A)]拷贝构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	~A()
	{
		cout << "[A::~A()析构函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void thread_work(int num)
	{
		cout << "子线程thread_work执行了" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void operator()(int num)
	{
		cout << "子线程()执行 " << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
};

void myprint(const int i, const A& pmybuf)
{
	cout << "&pmybuf的地址: " << &pmybuf << endl;  //这里打印的是pmybuf对象的地址
	return;
}

void myprint2(const A& pmybuf)
{
	//pmybuf.m_i = 199;  //我们修改该值不会影响到main函数
	cout << "子线程void myprint2(A& pmybuf)的参数地址是 " << &pmybuf << "  " << "this id = " << this_thread::get_id() << endl;
}

void myprint3(unique_ptr<int> p)
{

}

void printPA(const int i, const A& a)
{
	cout << &a << endl;
}

int main()
{
	cout << "主线程id: " << this_thread::get_id() << endl;
	int mvar = 1;
	thread mytobj2(myprint2, mvar);
	mytobj2.join();
	//mytobj2.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}
主线程id: 18960
[A::A(int a)构造函数执行]  00000009149FF664  threadid = 16412
子线程void myprint2(A& pmybuf)的参数地址是 00000009149FF664  this id = 16412
[A::~A()析构函数执行]  00000009149FF664  threadid = 16412
main主函数执行结束

注意:构造函数等都是在子线程中创造的,存在线程安全问题。

//将main()修改成如下:
int main()
{
	cout << "主线程id: " << this_thread::get_id() << endl;
	int mvar = 1;
	thread mytobj2(myprint2, A(mvar));
	//mytobj2.join();
	mytobj2.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}
主线程id: 25528
[A::A(int a)构造函数执行]  000000C58113FC34  threadid = 25528
[A::A(cosnt A)]拷贝构造函数执行]  000001DC49D827A0  threadid = 25528
[A::~A()析构函数执行]  000000C58113FC34  threadid = 25528
main主函数执行结束
子线程void myprint2(A& pmybuf)的参数地址是

若将void myprint2(A& pmybuf)更改为void myprint2(A pmybuf);

主线程id: 11000
[A::A(int a)构造函数执行]  0000005CAA4FF8C4  threadid = 11000
[A::A(cosnt A)]拷贝构造函数执行]  000001A4653F29D0  threadid = 11000
[A::~A()析构函数执行]  0000005CAA4FF8C4  threadid = 11000[A::A(cosnt A)]拷贝构造函数执行]
0000005CAA5FFC24  threadid = 23544
main主函数执行结束
子线程void myprint2(A& pmybuf)的参数地址是

多生成一个拷贝构造函数,尽量不这样使用。
疑问点:这个在子线程中多生成的拷贝构造函数,是通过主线程的A来传递,还是已经创建好的A来传递,会不会有线程安全问题?

三:传递类对象与智能指针作为线程参数

class A
{
public:
	mutable int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象。
	A(int a) :m_i(a)
	{
		cout << "[A::A(int a)构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	A(const A& a) :m_i(a.m_i)
	{
		cout << "[A::A(cosnt A)]拷贝构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	~A()
	{
		cout << "[A::~A()析构函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void thread_work(int num)
	{
		cout << "子线程thread_work执行了" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void operator()(int num)
	{
		cout << "子线程()执行 " << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
};

void myprint(const int i, const A& pmybuf)
{
	cout << "&pmybuf的地址: " << &pmybuf << endl;  //这里打印的是pmybuf对象的地址
	return;
}

void myprint2(const A& pmybuf)
{
	pmybuf.m_i = 199;  //我们修改该值不会影响到main函数
	cout << "子线程void myprint2(A& pmybuf)的参数地址是 " << &pmybuf << "  " << "this id = " << this_thread::get_id() << endl;
}

void myprint3(unique_ptr<int> p)
{

}

void printPA(const int i, const A& a)
{
	cout << &a << endl;
}

int main()
{
	cout << "主线程id: " << this_thread::get_id() << endl;
	A myobj(10);  //生成一个类对象
	thread mytobj(myprint2, myobj);  //myobj将类对象作为线程参数
	mytobj.join();
	//mytobj.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}

void myprint2(const A& pmybuf)
如果使用引用&作为参数,前面必须加const,否则大多数编译器会报错。

std::ref()函数,修改main()函数如下:

int main()
{
	cout << "主线程id: " << this_thread::get_id() << endl;
	A myobj(10);  //生成一个类对象
	thread mytobj(myprint2, std::ref(myobj));  //myobj将类对象作为线程参数
	mytobj.join();
	cout << "myobj.m_i = " << myobj.m_i << endl;
	//mytobj.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}
主线程id: 15708
[A::A(int a)构造函数执行]  0000004415F2F824  threadid = 15708
子线程void myprint2(A& pmybuf)的参数地址是 0000004415F2F824  this id = 20432
myobj.m_i = 199
main主函数执行结束
[A::~A()析构函数执行]  0000004415F2F824  threadid = 15708

若使用智能指针,main()修改为

void myprint3(unique_ptr<int> p)
{

}

int main()
{
	cout << "主线程id: " << this_thread::get_id() << endl;
	unique_ptr<int> myp(new int(100));
	thread mytobj(myprint3, std::move(myp));  //myobj将类对象作为线程参数
	mytobj.join();
	//mytobj.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}
主线程id: 19736
main主函数执行结束

一定不能detach,存在线程安全问题。

四:用成员函数指针做线程函数

class A
{
public:
	mutable int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象。
	A(int a) :m_i(a)
	{
		cout << "[A::A(int a)构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	A(const A& a) :m_i(a.m_i)
	{
		cout << "[A::A(cosnt A)]拷贝构造函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
	~A()
	{
		cout << "[A::~A()析构函数执行]" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void thread_work(int num)
	{
		m_i = num;
		cout << "子线程thread_work执行了" << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}

	void operator()(int num)
	{
		cout << "子线程()执行 " << "  " << this << "  " << "threadid = " << this_thread::get_id() << endl;
	}
};

void myprint(const int i, const A& pmybuf)
{
	cout << "&pmybuf的地址: " << &pmybuf << endl;  //这里打印的是pmybuf对象的地址
	return;
}

void myprint2(const A& pmybuf)
{
	pmybuf.m_i = 199;  //我们修改该值不会影响到main函数
	cout << "子线程void myprint2(A& pmybuf)的参数地址是 " << &pmybuf << "  " << "this id = " << this_thread::get_id() << endl;
}

void myprint3(unique_ptr<int> p)
{

}

void printPA(const int i, const A& a)
{
	cout << &a << endl;
}

int main()
{
	cout << "主线程id: " << this_thread::get_id() << endl;

	A mobj(10);  //生成一个类对象
	thread mytobj(&A::thread_work, &mobj, 15);  //&pa == std::ref()
	mytobj.join();
	//mytobj.detach();
	cout << "mobj.m_i = " << mobj.m_i << endl;
	cout << "main主函数执行结束" << endl;
	return 0;
}
主线程id: 1960
[A::A(int a)构造函数执行]  000000A0B01BF574  threadid = 1960
子线程thread_work执行了  000000A0B01BF574  threadid = 7904
mobj.m_i = 15
main主函数执行结束
[A::~A()析构函数执行]  000000A0B01BF574  threadid = 1960

&pa == std::ref();均没有调用拷贝构造函数,若使用detach,均存在线程安全问题。

添加A类对象函数,修改main()函数如下:

int main()
{
	cout << "主线程id: " << this_thread::get_id() << endl;
	A mobj(10);  //生成一个类对象
	thread mytobj(std::ref(mobj), 15);  //不再调用拷贝构造函数了,那么后续如果调用detach就不安全了
	mytobj.join();
	//mytobj.detach();
	cout << "mobj.m_i = " << mobj.m_i << endl;
	cout << "main主函数执行结束" << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值