C/C++线程退出的四种方法小结
作者:Jimmy1224
想要终止线程的运行,可以使用以下方法:
- 线程函数返回(最好使用该方法)。
- 同一个进程或另一个进程中的线程调用TerminateThread函数(应避免使用该方法)。
- 通过调用ExitThread函数,线程将自行撤消(最好不使用该方法)。
- ExitProcess和TerminateProcess函数也可以用来终止线程的运行(应避免使用该方法)。
下面将详细介绍终止线程运行的方法:1-4,并说明线程终止运行时会出现何种情况:5。
为了说明以下线程的退出时发生的动作,引入以下测试代码:
class TTThread { public: /** @name Constructors and Destructor*/ TTThread(); virtual ~TTThread(); public: //线程创建函数 BOOL Create(); //线程销毁,在这里验证TerminateThread void Destory(); //线程等待 BOOL Wait(DWORD dwWaitTime); inline DWORD GetThreadID() { return m_dwThreadID; } protected: //线程函数逻辑接口,子类继承 //在这里验证ExitThread和正常退出操作 virtual unsigned Process(); private: //线程入口地址 static unsigned __stdcall _threadProc(void *lpParam); private: HANDLE m_hThread; DWORD m_dwThreadID; }; //C++对象 class CObj { public: CObj() { printf("CObj create...\n"); } ~CObj() { printf("CObj delete...\n"); } }; //线程测试类 class CTestThread : public TTThread { public: virtual unsigned Process() { return 0; } };
线程函数返回
始终都应该将线程设计成这样的形式,即当想要线程终止运行时,它们就能够返回。这是确保所有线程资源被正确地清除的唯一办法。
如果线程能够返回,就可以确保下列事项的实现:
(1)在线程函数中创建的所有C++对象均将通过它们的析构函数进行释放。
(2)操作系统将正确地释放线程堆栈使用的内存。
(3)系统将线程的退出代码(在线程的内核对象中维护)设置为线程函数的返回值。
(4)系统将递减线程内核对象的使用计数。
其中,(1)(2)两点在编码中需要特别关注的,这个在编码规范上比较重要。(jimmy注)
测试代码:
int _tmain(int argc, _TCHAR* argv[]) { printf("main thread begin...\n"); //创建线程 CTestThread workthread; workthread.Create(); //等待线程结束,此时不会执行Destroy函数,线程正常结束 if (!workthread.Wait(3 * 1000)) { //超时销毁线程 workthread.Destory(); } printf("main thread end...\n"); return 0; }
测试结果:
main thread begin...
CObj create...//c++对象创建
current index:0
current index:1
current index:2
current index:3
current index:4
CObj delete...//C++对象析构以及线程申请的内存正确释放
main thread end...
请按任意键继续. . .
TerminateThread函数
调用TerminateThread函数也能够终止线程的运行,其函数原型如下:
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode);
TerminateThread能够撤消任何线程,其中hThread参数用于标识被终止运行的线程的句柄。当线程终止运行时,它的退出代码成为你作为dwExitCode参数传递的值。同时,线程的内核对象的使用计数也被递减。
注意TerminateThread函数是异步运行的函数,也就是说,它告诉系统你想要线程终止运行,但是,当函数返回时,不能保证线程被撤消。如果需要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数,传递线程的句柄。
用TerminateThread销毁线程示例代码:
// 在另一个线程中进行调用,这里是在主线程终止测试线程 void TTThread::Destory() { if (m_hThread) { //TerminateThread以异步方式执行,函数返回不代表线程结束, //线程函数停止执行,位置随机,类对象不会被析构导致内存泄漏 ::TerminateThread(m_hThread, 0); //等待线程结束 ::WaitForSingleObject(m_hThread, 500); ::CloseHandle(m_hThread); m_hThread = 0; m_dwThreadID = 0; printf("end destroy\n"); } }
设计良好的应用程序从来不使用这个函数,因为被终止运行的线程收不到它被撤消的通知。并且,如果使用TerminateThread,那么在拥有线程的进程终止运行之前,系统不撤消该线程的堆栈,造成内存不能及时释放。
int _tmain(int argc, _TCHAR* argv[]) { printf("main thread begin...\n"); //创建线程 CTestThread workthread; workthread.Create(); //等待线程结束,此时会执行Destroy函数,线程异常结束 if (!workthread.Wait(1 * 1000)) { //超时,主线程销毁测试线程 workthread.Destory(); } printf("main thread end...\n"); return 0; }
测试结果
main thread begin...
CObj create...
current index:0
current index:1
end destroy
main thread end...
//CObj没有执行析构函数,没有及时释放线程申请的_tiddata内存
请按任意键继续. . .
ExitThread函数
可以让线程调用ExitThread函数,以便强制终止当前线程运行,其函数原型:
VOID ExitThread(DWORD dwExitCode);
该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被析构。由于这个原因,最好从线程函数返回,而不是通过调用ExitThread来返回。
当然,可以使用ExitThread的dwExitThread参数告诉系统将线程的退出代码设置为什么。ExitThread函数并不返回任何值,因为线程已经终止运行,不能执行更多的代码。
注意终止线程运行的最佳方法是让它的线程函数返回。但是,如果使用本节介绍的方法,应该知道ExitThread函数是Windows用来撤消线程的函数。如果编写C/C++代码,那么决不应该调用ExitThread。应该使用Visual C++运行期库函数_endthreadex,因为_endthreadex可以确保,及时释放线程申请的tiddata内存。
测试代码
class CTestThread : public TTThread { public: virtual unsigned Process() { CObj obj; for (int i = 0; i < 5; i++) { Sleep(400); printf("current index:%d\n", i); //当前线程退出 if (i == 3) { printf("make thread end...\n"); //_endthreadex(0); ExitThread(0); } } return 0; } }; int _tmain(int argc, _TCHAR* argv[]) { printf("main thread begin...\n"); //创建线程 CTestThread workthread; workthread.Create(); //等待线程结束 if (workthread.Wait(3 * 1000)) { printf("the test thread end..."); } printf("main thread end...\n"); return 0; }
测试结果
main thread begin...
CObj create...
current index:0
current index:1
current index:2
current index:3
make thread end...线程被中断执行,造成tiddata和CObj内存泄漏
the test thread end...main thread end...
请按任意键继续. . .
在进程终止运行时撤消线程
ExitProcess和TerminateProcess函数也可以用来终止线程的运行。差别在于这些线程将会使终止运行的进程中的所有线程全部终止运行。另外,由于整个进程已经被关闭,进程使用的所有资源肯定已被清除。这当然包括所有线程的堆栈。这两个函数会导致进程中的剩余线程被强制撤消,就像从每个剩余的线程调用TerminateThread一样。显然,这意味着正确的应用程序清除没有发生,即C++对象撤消函数没有被调用,数据没有转至磁盘等等。
线程终止运行时发生的操作
当线程终止运行时,会发生下列操作:
(1)线程拥有的所有用户对象均被释放。在Windows中,大多数对象是由包含创建这些对象的线程的进程拥有的。但是一个线程拥有两个用户对象,即窗口和挂钩。当线程终止运行时,系统会自动撤消任何窗口,并且卸载线程创建的或安装的任何挂钩。其他对象只有在拥有线程的进程终止运行时才被撤消。
(2)线程的退出代码从STILL_ACTIVE改为传递给ExitThread或TerminateThread的代码。
(3)线程内核对象的状态变为已通知。
(4)如果线程是进程中最后一个活动线程,系统也将进程视为已经终止运行。
(5)线程内核对象的使用计数递减1。
当一个线程终止运行时,在与它相关联的线程内核对象的所有未结束的引用关闭之前,该内核对象不会自动被释放。
一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调用GetExitcodeThread来检查由hThread标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码:
函数原型:
BOOL GetExitCodeThread( HANDLE hThread, PDWORD pdwExitCode);
退出代码的值在pdwExitCode指向的DWORD中返回。如果调用GetExitCodeThread时线程尚未终止运行,该函数就用STILL_ACTIVE标识符(定义为0x103)填入DWORD。如果该函数运行成功,便返回TRUE。
线程基类整体实现:
/******************************************************* * @file TTThread.cpp * @author jimmy * @brief windows线程处理的包装 ******************************************************/ #include "stdafx.h" #include "TTThread.h" TTThread::TTThread() :m_hThread(0) { } TTThread::~TTThread() { if (m_hThread) { ::CloseHandle(m_hThread); } m_hThread = 0; } /************************************************************** * @brief : TTThread::create * * @param : -none * * @return : BOOL * * @author : Jimmy * * @date : 2019/2/13 星期三 * * @note : 线程创建函数 ***************************************************************/ BOOL TTThread::Create() { m_hThread = (HANDLE)_beginthreadex(0,0, _threadProc, this, 0, (unsigned*)&m_dwThreadID); if (0 == m_hThread) { m_dwThreadID = 0; } return m_hThread >(HANDLE)1; } void TTThread::Destory() { if (m_hThread) { //TerminateThread以异步方式执行,函数返回不代表线程结束, //线程函数停止执行,位置随机,类对象不会被析构导致内存泄漏 ::TerminateThread(m_hThread, 0); //等待线程结束 ::WaitForSingleObject(m_hThread, 500); ::CloseHandle(m_hThread); m_hThread = 0; m_dwThreadID = 0; printf("end destroy\n"); } } BOOL TTThread::Wait(DWORD dwWaitTime) { if (m_hThread == 0) return TRUE; return (::WaitForSingleObject(m_hThread, dwWaitTime) != WAIT_TIMEOUT); } unsigned TTThread::Process() { return 0; } unsigned __stdcall TTThread::_threadProc(void *lpParam) { TTThread* pThread = (TTThread*)lpParam; assert(pThread); if (pThread != 0) { pThread->Process(); } return 0; }
参考文章:
https://www.cnblogs.com/arsblog/p/4829729.html
完整线程类代码参见github:
https://github.com/jinxiang1224/cpp/tree/master/thread
到此这篇关于C/C++线程退出的四种方法小结的文章就介绍到这了,更多相关C/C++线程退出内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!