Windows下安全退出线程

1. 退出线程可以有四种方法

  • 线程函数的return返回(最好):

    • 其中用线程函数的return返回, 而终止线程是最安全的;
    • 在线程函数return返回后, 会清理函数内申请的类对象, 即调用这些对象的析构函数。
    • 然后会自动调用 _endthreadex()函数来清理 _beginthreadex(...)函数申请的资源(主要是创建的tiddata对象).
  • 调用 _endthreadex()函数或 ExitThread()函数(最好不要):
      如果使用这两种方法退出线程, 则不会执行线程函数的return语句, 所以就不会调用线程函数作用域内申请的类对象的析构函数, 会造成内存泄露.

  • 用同一个进程中的另一个线程调用 TerminateThread()函数(必须避免);

  • 终止该线程所在的进程(绝对避免);

注:

  • _endthreadex并不是一个过时的函数,正确的使用并不会带来问题。
    • 在线程的主函数中,return_endthreadex的一个良好替代
    • 就像main函数里面returnexit()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回调,引导线程自己退出。从而实现多个等待线程安全地退出等待状态。

猜你喜欢

转载自blog.csdn.net/VonSdite/article/details/81295120
今日推荐