MFC 线程的退出方法

A .线程函数的返回(推荐用法)(需要考虑的是 catch/运行标志/错误处理等方法
B. ExitThread函数(不推荐)
C.同一个进程或者另一个进程中的线程调用 TerminateThread函数( 应该避免这种方法)
D. 包含线程的进程终止( 应该避免使用这种方法)

线程函数的返回可以确保线程中的C++对象被撤销函数正确的撤销,操作系统正确的 释放线程堆栈所使用的内存,系统将线程的退出代码设置为线程函数的返回值,系统将递减线程内核对象的使用计数。而 ExitThread函数会促使系统清除该线程所使用的所有操作系统资源,但是 C++对象等资源将不被撤销(比如new变量),因此最好从线程函数返回。TerminateThread是异步运行函数,它告诉系统希望线程终止, 但不保证线程撤销的操作结果。如果需要确切的知道线程是否已经终止运行,需要调用 WaitForSingleObject或者类似的函数,传递线程的句柄(Learned@_@)异步的使用方法造成在 拥有线程的进程终止运行之前系统不撤销改线程的堆栈,这给其他 依然在执行的线程留下了使用数据的机会。但是除非必要该函数不推荐使用。
BOOL GetExitCodeThread(
HANDLE hThread,
PDWORD pdwExitCode);用于 检查hThread标志的线程是否终止运行,如果未终止,该函数使用 STILL_ACTIVE标识符填入DWORD,如果已经运行成功则返回TRUE。

使用线程退出的几种方法:

(1):

我想大部分人为了图方便,会定义一个BOOL变量如: BOOL g_bExtiThread ; 当 if( g_bExtiThread  == 0 )的时候跳出线程循环,结束线程;

既:g_bExtiThread = 0;只做这一步,会隐藏一个问题, 如果线程执行的时间较长,如循环中Sleep(1000);  这样会导致,执行 g_bExtiThread = 0; 后立即执行后面的函数,而不会等待线程结束如果线程中的变量与g_bExtiThread = 0;后面的执行相关,就可能隐藏问题;


(2):

这里自然,有人会用一个简单的方法避免这个过程就是:

g_bExtiThread = 0;
Sleep(2000);

这样, 等待线程结束后,执行其他语句;

这样做,有两个问题:1.效率上比较低,因为即使1000毫秒结束了,可是,却要等待Sleep(2000)

 2.如果上述修改为:

g_bExtiThread = 0;
Sleep(1001);

这样看着不错,但是,如果线程中,Sleep (1000),软后语句执行的时间,大于1毫秒,也就是说 线程循环一次的时间 大于1001毫秒,仍然可能导致 “(1)”中的问题;或者,线程很多,当线程执行到某一段的时候, CPU的时间片分配给其他线程,这样,依然会导致 线程中的循环时间无法确定;想通过Sleep的方法等待线程循环结束,只有将时间给的很长,但是这样太浪费时间和效率了。


(3):

比较通用的方法:

通过WaitForSingleObject获取线程状态,如果线程退出,执行后面的语句;

g_bExtiThread = 0;
WaitForSingleObject( pWinThreadtestexit->m_hThread, INFINITE );

示例:

UINT thread_testexit( PVOID pParam )
{
    while( g_bExtiThread )
    {
        Sleep(1000);
        static int i = 0;
        CString str;str.Format( L"%d",i++);
        //AfxGetApp()->GetMainWnd()->SetWindowText( str );
    }
    return 0;
}
void Ctmfc1Dlg::OnBnClickedButton1()
{
    // TODO: 在此添加控件通知处理程序代码
    pWinThreadtestexit = AfxBeginThread( thread_testexit, 0 );
}
void Ctmfc1Dlg::OnBnClickedButton2()
{
    // TODO: 在此添加控件通知处理程序代码 
    g_bExtiThread = 0;
    WaitForSingleObject( pWinThreadtestexit->m_hThread, INFINITE );
    SetWindowText( L"线程已经停止" ); 
}

(4):

这里我打算讲解一下在程序退出时,退出所有线程的一个更加通用的方法。道理上将,进程退出,进程中相关资源全部释放,包括线程,但是,大部分多线程情况,如果线程没有退出,会导致程序退出的时候崩溃,所以强烈建议所有线程都要退出。

方法:

//工作中线程:可以有多个相同或不同线程;这里一个举例;

bool   bIsThreadWorking = 1;
bool  bIsThreadStop    = 0;
uint WorkThread( void * pParam )
{
           while( bIsThreadWorking  )
               {
                     sleep(1000);
                      //...... 
              }
              bIsThreadStop = 1;//到这里表示已经跳出循环了,之后执行 return 0,退出线程;
             return 0;
}

//这个线程负责监控退出这个程序;
uint  ExitApplicationThread( void * pParam )
{
              while(1)
                     {
                                if( bIsThreadStop == 1 )
                                   {
                                         breadk;
                                   }
                                 sleep(10);  //
                      }
               sleep(100);//最好写上,确实等到别的线程退出;
               exit( 0 ); // 或者通知主程序退出,如 ::postMessage( afxGetApp()->m_pMainWind->m_hWnd, WM_CLOSE, 0, 0 );
              return 0;
}
//主线程:
void CMainDlg::ButtonExitApplication()
{
       bIsThreadWorking  = 1;
      AfxBeginThread( ExitApplicationThread, 0 );
    // HANDLE hThread = ::CreateThread( 0, 0, ExitApplicationThread, 0, NULL, NULL );
}

有人会疑问,为什么不直接将ExitApplicationThread中的代码写到CMainDlg::ButtonExitApplication()中,而是要重新启动一个线程呢?

解答:

    //注意:在windows上,AfxBeginThread 和 CreateThread 创建的线程是有一点区别的;

   //AfxBeginThread创建的线程,主线程的sleep函数可以阻塞AfxBeginThread创建的线程,也可以理解为只要主线程执行AfxBeginThread 创建的线程就没有机会执行,而且不仅仅是优先级的问题,而是MFC设计的问题;AfxBeginThread 创建的线程之间有相同的执行机会;而CreateThread创建的线程才可以和主线程一样,不论哪个线程执行,主线程和工作线程都有机会得到执行机会; 如果用CreateThread创建线程,就可以再主线程直接判断执行了;这个方法是比较好的方法,其实质和信号量的方法退出线程是一样的;如果是多线程的话,可以用链表线程退出变量保存,在退出循环中判断,直到所有线程退出变量bIsThreadStop 为真后再跳出ExitApplicationThread线程循环,结束主线程;


(5):

以下windows上退出线程的方法不推荐,可以了解;

BOOL TerminateThread(
  HANDLE hThread,    // handle to thread
  DWORD dwExitCode   // exit code
);

这里推荐一篇文章:

TerminateThread is a dangerous function that should only be used in the most extreme cases. You should call TerminateThread only if you know exactly what the target thread is doing, and you control all of the code that the target thread could possibly be running at the time of the termination. For example, TerminateThread can result in the following problems:

  • If the target thread owns a critical section, the critical section will not be released.(未释放互斥区,造成死锁)
  • If the target thread is allocating memory from the heap, the heap lock will not be released.(未释放堆分配锁,造成死锁)
  • If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread’s process could be inconsistent.(在执行内核函数时退出,造成该线程所在进程状态不确定,程序可能崩溃)
  • If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL.(在使用DLL时退出,造成DLL被销毁,其他使用该DLL得程序可能出现问题!)

thread cannot protect itself against TerminateThread, other than by controlling access to its handles. The thread handle returned by the CreateThread and CreateProcess functions has THREAD_TERMINATE access, so any caller holding one of these handles can terminate your thread


TerminateThread的方法不推荐使用

听过无数次不要TerminateThread,只是工作中常用,貌似也没有什么问题。今天在高强度测试中发现了一个不可原谅的错误。参看下面的例子

DWORD __stdcall mythread(void* )
{
    while( true )
    {
        char* p = new char[1024];

        delete p;
    }
}


int _tmain(int argc, _TCHAR* argv[])
{

    HANDLE h = CreateThread(NULL, 0, mythread, NULL, 0, NULL);

    Sleep(1000);

    TerminateThread(h, 0);
    h = NULL;

    char* p = new char[1024]; //这里会死锁,过不去 

    delete []p;

    return 0;
}

为什么死锁呢?new操作符用的是小块堆,整个进程在分配和回收内存时,都要用同一把锁。如果一个线程在占用该锁时被杀死(即临死前该线程在new或delete操作中),其他线程就无法再使用new或delete了,表现为hang住。

《核心编程》里明确提醒不要TerminateThread,但原因并不是血淋淋滴。今天发现的这个bug印证了此书的价值。

另注:许多临时的网络操作经常用TerminateThread,作为网络不通时的退出机制,以后要改改了。比如让该线程自生自灭,自行退出。

再推荐一篇文章:

CloseHandle(),TerminateThread(),ExitThread()的区别


线程的handle用处:

线程的handle是指向“线程的内核对象”的,而不是指向线程本身.每个内核对象只是内核分配的一个内存块,并且只能由内核访问。该内存块是一种数据结构,它的成员负责维护对象的各种信息(eg: 安全性描述,引用计数等)。


CloseHandle()

在CreateThread成功之后会返回一个hThread的handle,且内核对象的计数加1,CloseHandle之后,引用计数减1,当变为0时,系统删除内核对象。

但是这个handle并不能完全代表这个线程,它仅仅是线程的一个“标识”,系统和用户可以利用它对相应的线程进行必要的操纵。如果在线程成功创建后,不再需要用到这个句柄,就可以在创建成功后,线程退出前直接CloseHandle掉,但这并不会影响到线程的运行。


不执行CloseHandle() 带来的后果:

若在线程执行完之后,没有通过CloseHandle()将引用计数减1,在进程执行期间,将会造成内核对象的泄露,相当与句柄泄露,但不同于内存泄露, 这势必会对系统的效率带来一定程度上的负面影响。但是,请记住,当进程结束退出后,系统仍然会自动帮你清理这些资源。但是在这里不推荐这种做法,毕竟不是 一个良好的编程习惯!
( 应用程序运行时,有可能泄露内核对象,但是当进程终止运行时,系统能确保所有内容均被正确地清除。另外,这个情况是用于所有对象,资源和内存块,也就是说,当进程终止时,系统将保证不会留下任何对象。)


TerminateThread()

函数的声明如下:

BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode);
作用:
在线程外终止一个线程,用于强制终止线程。
参数说明:
HANDLE htread:被终止的线程的句柄,为CWinThread指针。
DWORD dwExitCode:退出码。
返回值:
函数执行成功则返回非零值,执行失败返回0。调用getlasterror获得返回的值。

 


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值