深入理解c++多线程:(一)detach使用隐患

一、其他构造线程的方法

c++线程,除了可以利用普通函数创建线程外,还有其他创建线程的方法:

  • 类对象
  • Lambda表达式

具体使用方法如下:

#include <thread>

using namespace std;

class TA
{
public:
	void operator ()()
	{
		printf("%s", "线程开始执行 \n");
	}
};

void main()
{
	TA ta;
	// thread 方法会调用TA的拷贝构造函数,所以使用detach方法,只要TA对象里没有引用和指针,就不会产生问题
	thread mThreadObj(ta);
	if (mThreadObj.joinable())
		mThreadObj.join();

	auto mLambdaThreaadObj = []
	{
		printf("%s", "线程开始执行 \n");
	};

	thread m_LambdaThread(mLambdaThreaadObj);
	if (m_LambdaThread.joinable())
		m_LambdaThread.join();
}

补充内容:
thread 方法会调用TA的拷贝构造函数,所以使用detach方法,只要TA对象里没有引用和指针,就不会产生问题,否则一旦传入主线程中的临时变量,并且主线程先于子线程执行完毕(可能会发生这种情况),那么程序运行情况就不可知,这是一定要避免的。

二、使用detach的隐患

首先,观察如下案例:

void mPrint(const int& i, char* pmBuff)
{
	printf("i : %d, buff: %s \n", i, pmBuff);
}

void main()
{
	int mI = 1;
	int& mIy = mI;
	char mBuff[]= "this is a test";

	thread mThread(mPrint,mI, mBuff);
	mThread.join();
}

在mThread线程中,我传入了两个局部变量,并且接收函数中有两个参数,一个是引用类型,一个是指针类型。

如果我将线程的控制由join变为detach会引发什么问题?

很容易想到的是,因为主线程和子线程的结束时间不可控,所以局部变量的销毁时间不可控,那程序的运行情况就是未知的。再细究,这里由两个参数,是两个参数都会受影响,还是其中一个,又可能是哪一个?

因此,我将两者地址都进行了比较:
在这里插入图片描述

可以看到,引用类型的参数内存地址和主线程中不一样,指针类型的参数和主线程中的一样。因此可以得到结论,如果线程中的形参是引用类型,那主线程会将传入的值做一次赋值,所以不是真的直接引用绑定,但是如果形参是指针类型,那就不会拷贝

但如何避免这种情况呢?

我起初的思路是,只要再参数传递过程中,产生了拷贝那就是安全的(并不正确),因此我总结了c++中会调用拷贝构造函数的三种情形:

  • 当类的一个对象去初始化该类的另一个对象时,注意是初始化不是赋值
  • 如果函数的形参是类的对象,调用函数参数是值传递;
  • 如果函数的返回值是类对象,函数调用完成返回时

这里适用的就是第二种情况。

但是,我又反应过来,这个拷贝构造函数发生的时间是在主线程之前还是之后呢?如果是在主线程结束之后才调用,那detach的风险并没有规避掉,因此我又做了如下尝试:

#include <thread>

using namespace std;

class TA
{
public:
	int m_i;
	TA(const int& i) :m_i(i)
	{
		printf("%s", "拷贝构造函数执行 \n");
	};
	void operator ()()
	{
		printf("%s", "线程开始执行 \n");
	}
};

void mPrint( char* pmBuff,const TA&ta)
{
	printf("i : %d, buff: %s \n",ta.m_i, pmBuff);
}

void main()
{
	int mI = 1;
	int& mIy = mI;
	char mBuff[]= "this is a test";

	//利用类型转化构造函数将整型转成TA类型
	thread mThread(mPrint, mBuff,mI);
	mThread.detach();
}

因为thread肯定会调用传入对象的拷贝构造函数,所以如果输出中没有显示拷贝构造函数的执行,那么就说明,拷贝可能发生在主线程结束之后。
执行结果:
在这里插入图片描述
结论:
这样也是不安全的。

那究竟如何解决detach的这个隐患呢?

通过翻阅资料发现,只需使用临时对象即可完美规避这个问题。
如下所示:

	thread mThread(mPrint, mBuff,TA(mI));

三、结论

1、如果传递简单类型参数,使用值传递,不要使用引用传递,杜绝使用指针传递;
2、如果传递类对象,在创建线程时就构造临时对象,在函数参数里,使用引用来接 。

(建议一律使用join,能避免90%的问题)

以上就是本篇文章的全部内容,如果不足,请批评指正。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
VS2010中使用C++创建和使用DL.docx,文档加代码,全了。工程代码下载: 1.生成动态链接库(_declspec(dllexport)方式导出函数) 2.生成动态链接库(以.def文件(模块定义文件)方式导出函数) 3.以加载时动态链接方式调用DLL 4.以运行时动态链接方式调用DLL 5.以模块定义方式(.def文件)建立的动态链接库的调用 遇到的问题: 1.库导入的时候目录的问题。对应文中的问题1,后面有解释。 2.字符集的问题(是Unicode字符集还是多字节集),两种方案,一种修改字符集为多字节集,二是将字符串前面加 _T(""),文中问题2 3.不知道怎么通过模块定义文件方式生成DLL,通过看参考博客的代码找到了答案,主要修改头文件,和添加模块定义文件。 4.模块定义文件中的库文件名应和工程名一致。 DllMain函数 Windows在加载DLL时,需要一个入口函数,就像控制台程序需要main函数一样。有的时候,DLL并没有提供DllMain函数,应用程序也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的默认DllMain函数版本,并不意味着DLL可以抛弃DllMain函数。 根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数,这就说明不能在客户端直接调用DllMain函数,DllMain函数是自动被调用的。 DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DllMain函数也被调用。参数ul_reason_for_call指明了调用DllMain的原因,有以下四种情况: DLL_PROCESS_ATTACH:当一个DLL被首次载入进程地址空间时,系统会调用该DLL的DllMain函数,传递的ul_reason_for_call参数值为DLL_PROCESS_ATTACH。这种情况只有首次映射DLL时才发生; DLL_THREAD_ATTACH:该通知告诉所有的DLL执行线程的初始化。当进程创建一个新的线程时,系统会查看进程地址空间中所有的DLL文件映射,之后用DLL_THREAD_ATTACH来调用DLL中的DllMain函数。要注意的是,系统不会为进程的主线程使用值DLL_THREAD_ATTACH来调用DLL中的DllMain函数; DLL_PROCESS_DETACH:当DLL从进程的地址空间解除映射时,参数ul_reason_for_call参数值为DLL_PROCESS_DETACH。当DLL处理DLL_PROCESS_DETACH时,DLL应该处理与进程相关的清理操作。如果进程的终结是因为系统中有某个线程调用了TerminateProcess来终结的,那么系统就不会用DLL_PROCESS_DETACH来调用DLL中的DllMain函数来执行进程的清理工作。这样就会造成数据丢失; DLL_THREAD_DETACH:该通知告诉所有的DLL执行线程的清理工作。注意的是如果线程的终结是使用TerminateThread来完成的,那么系统将不会使用值DLL_THREAD_DETACH来执行线程的清理工作,这也就是说可能会造成数据丢失,所以不要使用TerminateThread来终结线程。以上所有讲解在工程DLLMainDemo(工程下载)都有体现。 函数导出方式

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值