1. 退出线程可以有四种方法
线程函数的return返回(最好):
- 其中用线程函数的return返回, 而终止线程是最安全的;
- 在线程函数return返回后, 会清理函数内申请的类对象, 即调用这些对象的析构函数。
- 然后会自动调用
_endthreadex()
函数来清理_beginthreadex(...)
函数申请的资源(主要是创建的tiddata对象).
调用
_endthreadex()
函数或ExitThread()
函数(最好不要):
如果使用这两种方法退出线程, 则不会执行线程函数的return语句, 所以就不会调用线程函数作用域内申请的类对象的析构函数, 会造成内存泄露.用同一个进程中的另一个线程调用 TerminateThread()函数(必须避免);
终止该线程所在的进程(绝对避免);
注:
_endthreadex
并不是一个过时的函数,正确的使用并不会带来问题。
- 在线程的主函数中,
return
是_endthreadex
的一个良好替代 - 就像main函数里面
return
是exit()
或ExitProccess()
的良好替代一样 - 但是这不表示
exit
函数没用。比如线程调用了一个子函数,如果子函数决定退出线程,return
是没用的,_endthreadex
即可终结线程。 - 但问题是,子线程用
_endthreadex
可能导致内存泄漏,而子线程并不像主线程一样,主线程退出了,进程也就退出了,OS会清理一切资源,无所谓泄露不泄露,而子线程退出后主线程可能还会运行很久,并且可能有大量的同类型的子线程退出,会造成要命的泄露
- 在线程的主函数中,
2. 使用线程APC回调安全退出多个等待线程
程序开发中经常遇到需要这些情况:
- 辅助线程正在等待内核对象的触发,主线程需要强制终止辅助线程。
- 使用
TerminateThread
来强制终止线程当然是不太好的,强制终止线程后系统不会销毁此线程的堆栈,长久下去内存泄露问题就会很严重了。 - 线程最安全的退出方式是让它自己返回了。
- 一种安全退出线程方式:使用可等待API等待内核对象触发,添加线程APC回调。
API介绍
DWORD WINAPI QueueUserAPC(
__in PAPCFUNC pfnAPC,
__in HANDLE hThread,
__in ULONG_PTR dwData
);
- 函数允许我们手动将一个APC回调添加到指定线程队列中,回调函数:
VOID WINAPI ApcCallback1(ULONG_PTR dwParam)
- 这个线程可以是系统中任何线程,如果标识的线程在另一个进程中,那么回调函数的地址也必须在另一个线程的地址空间中。
QueueUserAPC
也可以用来强制让线程退出等待状态,当线程正在等待内核对象触发,可以使用它强制唤醒正在等待的线程,并让它将自己杀死。另外就是Windows提供了4个API,可将线程的状态设置为可提醒状态。
扫描二维码关注公众号,回复: 2660879 查看本文章
SleepEx(
__in DWORD dwMilliseconds,
__in BOOL bAlertable
);
WaitForSingleObjectEx(
__in HANDLE hHandle,
__in DWORD dwMilliseconds,
__in BOOL bAlertable
);
WaitForMultipleObjectsEx(
__in DWORD nCount,
__in_ecount(nCount) CONST HANDLE *lpHandles,
__in BOOL bWaitAll,
__in DWORD dwMilliseconds,
__in BOOL bAlertable
);
SignalObjectAndWait(
__in HANDLE hObjectToSignal,
__in HANDLE hObjectToWaitOn,
__in DWORD dwMilliseconds,
__in BOOL bAlertable
);
实例代码
//给每一个线程标记上一个索引值
volatile LONG g_nIndex = 0;
UINT __stdcall Thread1(void* lpParam)
{
//使用原子锁来操作实现用户模式下的线程同步,更加快捷
LONG lPrevIndex = InterlockedExchangeAdd(&g_nIndex, 1);//我们需要记录原始值
LONG lCurIndex = lPrevIndex + 1;
cout<<"线程"<<lCurIndex<<"开始执行"<<endl;
while( true )
{
//cout<<"Thread1:"<<nIndex<<endl;
//休眠一秒钟,线程进入可提醒状态
if ( WAIT_IO_COMPLETION == SleepEx(1000, TRUE) )
{//此时,我们添加一项到APC队列中了,线程可以退出了
break;
}
//nIndex++;
}
cout<<"线程"<<lCurIndex<<"正常退出"<<endl;
return 0;
}
//APC回调函数
VOID WINAPI ApcCallback(ULONG_PTR dwParam)
{
//由于只是为了让线程立即正常退出,而不必杀死线程,我们在这里可以不做任何处理
cout<<"ApcCallback:线程的APC回调函数执行"<<endl;
}
bool ApcTest()
{
const int nThreadCount = 10;
HANDLE hThread[nThreadCount];
int i = 0;
for ( int i=0; i<nThreadCount; ++i )
hThread[i] = (HANDLE)_beginthreadex(NULL, 0, Thread1, NULL, 0, NULL);
DWORD dwError, dwRet;
while( true )
{
dwRet = WaitForMultipleObjects(nThreadCount, hThread, TRUE, 10*1000);
if ( dwRet == WAIT_TIMEOUT )
{//等待超时,想办法强制终止线程
//由于辅助线程休眠时进入可等待状态,手动添加回调到APC队列将导致毁掉执行完后线程清空队列,线程退出可提醒状态
//此时,友好地退出线程。
for ( i=0; i<nThreadCount; ++i )
{
dwRet = QueueUserAPC(ApcCallback, hThread[i], 2015);
if ( dwRet == 0 )
{//添加APC回调到线程APC队列失败
dwError = GetLastError();
break;
}
}
continue;
}
if ( WAIT_OBJECT_0 == dwRet )
{
cout<<"辅助线程均已退出,循环退出…………"<<endl;
break;
}
}
for ( i=0; i<nThreadCount; ++i )
CloseHandle(hThread[i]);
return true;
}
程序运行
总结
- 主要就是在线程等待时,使用那些扩展的API(带Ex),使线程进入可等待状态。这时,系统会先检查线程的APC队列。由于我们向线程的APC队列中人为添加了一个回调函数,队列不为空则调用APC函数,清空队列。然后函数执行完毕返回
WAIT_IO_COMPLETION
,这时我们就知道是自己添加了一个APC回调,引导线程自己退出。从而实现多个等待线程安全地退出等待状态。