std::thread 使用局部变量问题
std::thread 使C++能方便地使用线程,特别是使用Lambda表达式作为执行函数。但在使用Lambda表达式作为执行函数时或传递参数,却有一个很大的坑,就是局部变量作为参数。
示例:
CProgressDlg prodlgTemp;
SCallBackParam param;
prodlgTemp.SetCallBackFunc(ExecuteCallBackProgress, (LPVOID)¶m);
prodlgTemp.DoModal();
LONG ExecuteCallBackProgress(LPVOID lpParam)
{
//进度条对象指针
SCallBackParam *pParam = reinterpret_cast<SCallBackParam*>(lpParam);
CProgressDlg* pProgressDlg = (CProgressDlg *)pParam->m_lpParam2;
SCallBackParam param;
param.m_lpParam1 = (LPVOID)pProgressDlg;
std::thread td([¶m]()
{
int nType = CProgressDlg::EPTYPE_BEG;
param.m_lpParam2 = (LPVOID)&nType;
int nCount = 0;
while (nCount < 100)
{
Sleep(50);
nCount++;
int nProgress = nCount;
param.m_lpParam3 = (LPVOID)&nProgress;
CProgressDlg::SetProgressFuncCallBack(¶m);
}
}
});
return 0;
}
上述代码简单演示实现一个进度条功能,进度条窗口CProgressDlg,通过回调函数ExecuteCallBackProgress实现进度数值的显示。运行以上代码,程序会崩溃,提示abort错误。以上代码有两个问题:1)线程对象td没有调用detach()函数。thread::detach(): 从 thread 对象分离执行的线程,允许执行独立地持续。一旦线程退出,则释放所有分配的资源。调用detach后, *this 不再占有任何线程。上例代码,td对象消亡时,由于前面没有调用join()或者detach(),那它是joinable的,析构函数会调用terminate(),terminate()会调用abort(), abort()是非正常结束进程,不进行任何清理工作,直接终止程序。如果td不是joinable的,就不会引发terminate而非正常结束进程。2)用引用(或指针)传递局部变量参数。在线程分离的情况下,Lambda表达用引用捕获参数,当线程函数执行完后,会出现局部变量先被释放的情况,这时会引发内存指针无效的崩溃。所以在用Lambda表达式或函数参数时,一定要用值传递,并且要注意Lambda表达式不能有修改参数的目的。以上错误,如果没有这种意识是很难发现问题的,特别是问题2,明明主线程被窗口DoModal()阻塞了,线程执行函数还会出现窗口对象指针无效的错误,这种错误就会让人感觉莫名奇妙,也会花费大量的时间去调试。
正确的代码如下:
LONG ExecuteCallBackProgress(LPVOID lpParam)
{
//进度条对象指针
SCallBackParam *pParam = reinterpret_cast<SCallBackParam*>(lpParam);
CProgressDlg* pProgressDlg = (CProgressDlg *)pParam->m_lpParam2;
SCallBackParam param;
param.m_lpParam1 = (LPVOID)pProgressDlg;
std::thread td([param]mutable()
{
int nType = CProgressDlg::EPTYPE_BEG;
param.m_lpParam2 = (LPVOID)&nType;
int nCount = 0;
while (nCount < 100)
{
Sleep(50);
nCount++;
int nProgress = nCount;
param.m_lpParam3 = (LPVOID)&nProgress;
CProgressDlg::SetProgressFuncCallBack(¶m);
}
}
});
td.detach();
return 0;
}
或
LONG ExecuteCallBackProgress(LPVOID lpParam)
{
//进度条对象指针
SCallBackParam *pParam = reinterpret_cast<SCallBackParam*>(lpParam);
CProgressDlg* pProgressDlg = (CProgressDlg *)pParam->m_lpParam2;
SCallBackParam param;
param.m_lpParam1 = (LPVOID)pProgressDlg;
std::thread td([](SCallBackParam param)
{
int nType = CProgressDlg::EPTYPE_BEG;
param.m_lpParam2 = (LPVOID)&nType;
int nCount = 0;
while (nCount < 100)
{
Sleep(50);
nCount++;
int nProgress = nCount;
param.m_lpParam3 = (LPVOID)&nProgress;
CProgressDlg::SetProgressFuncCallBack(¶m);
}
}
},param);
td.detach();
return 0;
}