C++ 并发与多线程(二)

2021/5/3 14:25:32

本文主要是介绍C++ 并发与多线程(二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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

  • 1.传递临时对象作为线程参数
    • 1.1 要避免的陷阱(解释1)
    • 1.2 要避免的陷阱2
    • 1.3 总结
      • 1.若传递int这种简单类型参数,建议直接用值传递,不要用引用。
      • 2.如果传递类对象,避免隐式类型转换。要在创建线程这一行构造出临时对象来,然后在函数参数里用引用来接,否则系统还会构造一次。
      • 3. 建议不使用detach,只使用join,这样就不存在局部变量失效导致线程对内存的非法引用。
  • 2.临时对象作为线程参数继续讲
    • 2.1线程id的概念
    • 2.2临时对象构造时机抓捕
  • 3.传递类对象、智能指针作为线程参数

1.传递临时对象作为线程参数

1.1 要避免的陷阱(解释1)

先分析参数i,分析可知,对于i来说 线程中的函数的本来是要使用引用传递,但是实际上使用的是值传递,因此即便主线程中使用了detach使主线程先结束,子线程依然是安全的。

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


void myprint(const int &i, char *pmybuf)
{
	//分析认为 i并不是mavr的引用,实际是值传递
	//即便主线程中detach,那么子线程依然是安全的
	cout << i << endl;
	cout << pmybuf << endl;
}

int main()
{

	//一:传递临时对象作为线程参数
	//1.1要避免的陷阱(解释1)
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is a test!";
	thread mytobj(myprint, mvar, mybuf);
	//mytobj.join();
	mytobj.detach(); //子线程和主线程分别执行

	cout << "I LOVE CHINA" << endl;
	return 0;
}

使用shift+F9 参看相应参数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
再来看参数pmybuf,对于原字符数组 ,地址为affc38
(设置两个断点,F5快速到下一个断点)
在这里插入图片描述
而传入函数中数组的地址为:
在这里插入图片描述
两者地址相同,这里使用了引用传递,这导致子线程是不安全的。所以在使用detach时,最好不使用引用和指针。

1.2 要避免的陷阱2

当我们将子线程执行函数中的第二个参数,即char * pmybuf修改为const string &pmybuf时,我们再查看进入线程前后的地址。进入子线程前的地址和进入子线程后的地址不相同,说明是值传递,但是这样的子线程真的安全吗?
事实上存在mybuf都被回收了(main函数执行完了),系统才用mybuf去转string的情况

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


void myprint(const int &i, const string &pmybuf)
{
	//分析认为 i并不是mavr的引用,实际是值传递
	//即便主线程中detach,那么子线程依然是安全的
	cout << i << endl;
	cout << pmybuf << endl;  //指针在detach指针时,绝对有问题
}

int main()
{

	//一:传递临时对象作为线程参数
	//1.1要避免的陷阱(解释1)
	//1.2 要避免的陷阱(解释2)
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is a test!";
	thread mytobj(myprint, mvar, mybuf);         //mybuf具体在什么时候转换成了string
	//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string
	//mytobj.join();
	mytobj.detach(); //子线程和主线程分别执行

	cout << "I LOVE CHINA" << endl;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
那应该怎么才是稳定的呢?
首先我们将代码做一下修改:
创建A类 分别写出有参构造 拷贝构造和析构函数,然后在线程函数中传入需要的参数,使用join()函数,结果如图:

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


class A
{
public:
	int m_i;
	//类型转换构造函数 将int整形转换成类对象
	A(int a) :m_i(a)         //初始化变量m_i  相当于在函数内实现了m_i = a
	{
		cout << "[A::A(int a)构造函数执行]" << endl;
	}
	A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << endl; }
	~A()
	{
		cout << "[A::~A(析构函数执行]" << endl;
	}

};

//void myprint(const int &i, const string &pmybuf)
//{
//	//分析认为 i并不是mavr的引用,实际是值传递
//	//即便主线程中detach,那么子线程依然是安全的
//	cout << i << endl;
//	cout << pmybuf << endl;  //指针在detach指针时,绝对有问题
//}


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

int main()
{

	//一:传递临时对象作为线程参数
	//1.1要避免的陷阱(解释1)
	//1.2 要避免的陷阱(解释2)
	//int mvar = 1;
	//int& mvary = mvar;
	//char mybuf[] = "this is a test!";

	//thread mytobj(myprint, mvar, mybuf);         //mybuf具体在什么时候转换成了string
	//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string

	//我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象  
	//用自定义的类class A来求证
	//thread mytobj(myprint, mvar, string(mybuf));

	int mvar = 1;
	int mysecondpar = 12;
	thread mytobj(myprint, mvar, mysecondpar);     //这是希望将整形转换成A类对象传递给myprint函数的第二个参数

	mytobj.join();
	

	cout << "I LOVE CHINA" << endl; 
	return 0;
}



先执行构造函数,再执行析构函数,通过有参构造函数成功的将int类型的mysecondpar转换成A类对象。
在这里插入图片描述
将join函数改为detach函数后,将主函数里的cout注释掉,使主函数快速结束,能观察到如下结果:
在这里插入图片描述
没有打印任何东西,说明线程执行函数内还没来得及通过有参构造来创建类对象,主线程就结束了,这里子线程就会出现问题。
解决这个问题的办法是创建临时对象

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


class A
{
public:
	int m_i;
	//类型转换构造函数 将int整形转换成类对象
	A(int a) :m_i(a)         //初始化变量m_i  相当于在函数内实现了m_i = a
	{
		cout << "[A::A(int a)构造函数执行]" << endl;
	}
	A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << endl; }
	~A()
	{
		cout << "[A::~A(析构函数执行]" << endl;
	}

};

//void myprint(const int &i, const string &pmybuf)
//{
//	//分析认为 i并不是mavr的引用,实际是值传递
//	//即便主线程中detach,那么子线程依然是安全的
//	cout << i << endl;
//	cout << pmybuf << endl;  //指针在detach指针时,绝对有问题
//}


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

int main()
{

	//一:传递临时对象作为线程参数
	//1.1要避免的陷阱(解释1)
	//1.2 要避免的陷阱(解释2)
	//int mvar = 1;
	//int& mvary = mvar;
	//char mybuf[] = "this is a test!";

	//thread mytobj(myprint, mvar, mybuf);         //mybuf具体在什么时候转换成了string
	//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string

	//我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象  
	//用自定义的类class A来求证
	//thread mytobj(myprint, mvar, string(mybuf));

	int mvar = 1;
	int mysecondpar = 12;

	//thread mytobj(myprint, mvar, mysecondpar);     //这是希望将整形转换成A类对象传递给myprint函数的第二个参数

	thread mytobj(myprint, mvar, A(mysecondpar));     //通过创建临时对象来解决上面这个问题
	//mytobj.join();
	mytobj.detach();

	//cout << "I LOVE CHINA" << endl; 
	return 0;
}

运行结果
在这里插入图片描述
这里说明了临时对象是在主线程中被创建的,即创建线程的同时,通过构造临时对象来传递参数的方法时可行。
事实1:只要用临时构造的A类对象作为参数传递给子线程,那么这一定能够在主线程结束前把子线程的第二个参数构件出来,从而确保即便是detach了,主线程结束了,子线程不会使用无效参数

1.3 总结

1.若传递int这种简单类型参数,建议直接用值传递,不要用引用。

2.如果传递类对象,避免隐式类型转换。要在创建线程这一行构造出临时对象来,然后在函数参数里用引用来接,否则系统还会构造一次。

3. 建议不使用detach,只使用join,这样就不存在局部变量失效导致线程对内存的非法引用。

2.临时对象作为线程参数继续讲

2.1线程id的概念

**每个线程实际上都对应一个数字,每个线程对应的数字都不同。
线程id可以使用标准函数获取 std::this_thread::get_id()

2.2临时对象构造时机抓捕

为了验证隐式类型转换是在子线程中进行的,我们可以使用获取线程id的方法,代码如下:**

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


class A
{
public:
	int m_i;
	//类型转换构造函数 将int整形转换成类对象
	A(int a) :m_i(a)         //初始化变量m_i  相当于在函数内实现了m_i = a
	{
		cout << "[A::A(int a)构造函数执行]" <<this<<"this_threadid"<<this_thread::get_id()<< endl;
	}
	A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" <<this<< "this_threadid" << this_thread::get_id() << endl; }
	~A()
	{
		cout << "[A::~A(析构函数执行]" <<this<< "this_threadid" << this_thread::get_id() << endl;
	}

};

//void myprint(const int &i, const string &pmybuf)
//{
//	//分析认为 i并不是mavr的引用,实际是值传递
//	//即便主线程中detach,那么子线程依然是安全的
//	cout << i << endl;
//	cout << pmybuf << endl;  //指针在detach指针时,绝对有问题
//}


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

void myprint2(const A &pmybuf)
{

	cout <<"子线程参数地址为:"<< &pmybuf << endl;			//打印pmybuf对象的地址
	cout << "threadid = " << this_thread::get_id() << endl;
	return;
}

int main()
{

	//一:传递临时对象作为线程参数
	//1.1要避免的陷阱(解释1)
	//1.2 要避免的陷阱(解释2)
	//int mvar = 1;
	//int& mvary = mvar;
	//char mybuf[] = "this is a test!";

	//thread mytobj(myprint, mvar, mybuf);         //mybuf具体在什么时候转换成了string
	//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string

	//我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象  
	//用自定义的类class A来求证
	//thread mytobj(myprint, mvar, string(mybuf));

	//int mvar = 1;
	//int mysecondpar = 12;

	thread mytobj(myprint, mvar, mysecondpar);     //这是希望将整形转换成A类对象传递给myprint函数的第二个参数

	//thread mytobj(myprint, mvar, A(mysecondpar));     //通过创建临时对象来解决上面这个问题
	//mytobj.join();


	cout << "主线程id为:" << this_thread::get_id() << endl;
	int mvar = 1;
	std::thread mytobj(myprint2, mvar);
	mytobj.join();

	//cout << "I LOVE CHINA" << endl; 
	return 0;
}

结果为下图,可以看出将int转换为A类的过程是在子线程中进行的。
在这里插入图片描述
如果改为std::thread mytobj(myprint2, A(mvar)); 观察结果
在这里插入图片描述
用了临时对象后,可以安全使用。
即便使用detach,也可以安全使用

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

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


class A
{
public:
	mutable int m_i;             //mutable关键字可以修改const常量
	//类型转换构造函数 将int整形转换成类对象
	A(int a) :m_i(a)         //初始化变量m_i  相当于在函数内实现了m_i = a
	{
		cout << "[A::A(int a)构造函数执行]" <<this<<"this_threadid"<<this_thread::get_id()<< endl;
	}
	A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" <<this<< "this_threadid" << this_thread::get_id() << endl; }
	~A()
	{
		cout << "[A::~A(析构函数执行]" <<this<< "this_threadid" << this_thread::get_id() << endl;
	}

};

//void myprint(const int &i, const string &pmybuf)
//{
//	//分析认为 i并不是mavr的引用,实际是值传递
//	//即便主线程中detach,那么子线程依然是安全的
//	cout << i << endl;
//	cout << pmybuf << endl;  //指针在detach指针时,绝对有问题
//}


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

void myprint2(const A &pmybuf)
{
	pmybuf.m_i = 199;            

	cout <<"子线程参数地址为:"<< &pmybuf << endl;			//打印pmybuf对象的地址
	cout << "threadid = " << this_thread::get_id() << endl;
	return;
}

int main()
{

	//一:传递临时对象作为线程参数
	//1.1要避免的陷阱(解释1)
	//1.2 要避免的陷阱(解释2)
	//int mvar = 1;
	//int& mvary = mvar;
	//char mybuf[] = "this is a test!";

	//thread mytobj(myprint, mvar, mybuf);         //mybuf具体在什么时候转换成了string
	//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string

	//我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象  
	//用自定义的类class A来求证
	//thread mytobj(myprint, mvar, string(mybuf));

	//int mvar = 1;
	//int mysecondpar = 12;

	thread mytobj(myprint, mvar, mysecondpar);     //这是希望将整形转换成A类对象传递给myprint函数的第二个参数

	//thread mytobj(myprint, mvar, A(mysecondpar));     //通过创建临时对象来解决上面这个问题
	//mytobj.join();


	/*cout << "主线程id为:" << this_thread::get_id() << endl;
	int mvar = 1;
	std::thread mytobj(myprint2, A(mvar));
	mytobj.join();*/


	A myobj(10);                  //生成一个类A对象 
	thread mytobj(myprint2, myobj);          //myobj将类对象作为线程参数 调用了拷贝构造 主线程结束不影响子线程进行
	mytobj.join();


	//cout << "I LOVE CHINA" << endl; 
	return 0;
}



在这里插入图片描述
在这里插入图片描述



这篇关于C++ 并发与多线程(二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程