windows 不使用 TerminateThread() 优雅地终止线程

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1702172/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 13:24:52  来源:igfitidea点击:

Terminating a thread gracefully not using TerminateThread()

cwindowsmultithreading

提问by Uri

My application creates a thread and that runs in the background all the time. I can only terminate the thread manually, not from within the thread callback function. At the moment I am using TerminateThread()to kill that thread but it's causing it to hang sometimes. I know there is a way to use events and WaitForSingleObject()to make the thread terminate gracefully but I can't find an example about that.

我的应用程序创建了一个线程并且一直在后台运行。我只能手动终止线程,而不能从线程回调函数中终止。目前我正在使用TerminateThread()杀死该线程,但有时会导致它挂起。我知道有一种方法可以使用事件WaitForSingleObject()并使线程正常终止,但我找不到关于此的示例。

Please, code is needed here.

拜托,这里需要代码。

回答by Tim Sylvester

TerminateThread is a bad idea, especially if your thread uses synchronization objects such as mutexes. It can lead to unreleased memory and handles, and to deadlocks, so you're correct that you need to do something else.

TerminateThread 是一个坏主意,尤其是当您的线程使用同步对象(例如互斥锁)时。它可能导致未释放的内存和句柄以及死锁,因此您需要做其他事情是正确的。

Typically, the way that a thread terminates is to return from the function that defines the thread. The main thread signals the worker thread to exit using an event object or a even a simple boolean if it's checked often enough. If the worker thread waits with WaitForSingleObject, you may need to change it to a WaitForMultipleObjects, where one of the objects is an event. The main thread would call SetEventand the worker thread would wake up and return.

通常,线程终止的方式是从定义线程的函数返回。主线程使用事件对象或什至是一个简单的布尔值来通知工作线程退出,如果它被经常检查的话。如果工作线程等待WaitForSingleObject,您可能需要将其更改为WaitForMultipleObjects,其中一个对象是事件。主线程将调用SetEvent,工作线程将唤醒并返回。

We really can't provide any useful code unless you show us what you're doing. Depending on what the worker thread is doing and how your main thread is communicating information to it, it could look very different.

除非您向我们展示您在做什么,否则我们真的无法提供任何有用的代码。根据工作线程正在做什么以及您的主线程如何与它通信,它看起来可能非常不同。

Also, under [now very old] MSVC, you need to use _beginthreadexinstead of CreateThreadin order to avoid memory leaks in the CRT. See MSKB #104641.

此外,在 [现在很旧] MSVC 下,您需要使用_beginthreadex而不是CreateThread为了避免 CRT 中的内存泄漏。参见MSKB #104641

Update:

更新:

One use of worker thread is as a "timer", to do some operation on regular intervals. At the most trivial:

工作线程的一种用途是作为“计时器”,定期执行某些操作。最微不足道的:

for (;;) {
    switch (WaitForSingleObject(kill_event, timeout)) {
        case WAIT_TIMEOUT: /*do timer action*/ break;
        default: return 0; /* exit the thread */
    }
}

Another use is to do something on-demand. Basically the same, but with the timeout set to INFINITEand doing some action on WAIT_OBJECT_0instead of WAIT_TIMEOUT. In this case you would need two events, one to make the thread wake up and do some action, another to make it wake up and quit:

另一个用途是按需执行某些操作。基本相同,但超时设置为INFINITE并在WAIT_OBJECT_0而不是 上执行一些操作WAIT_TIMEOUT。在这种情况下,您需要两个事件,一个使线程唤醒并执行某些操作,另一个使其唤醒并退出:

HANDLE handles[2] = { action_handle, quit_handle };
for (;;) {
    switch (WaitForMultipleObject(handles, 2, FALSE, INFINITE)) {
        case WAIT_OBJECT_0 + 0: /* do action */ break;
        default:
        case WAIT_OBJECT_0 + 1: /* quit */ break;
    }
}

Note that it's important that the loop do something reasonable if WFSO/WFMO return an error instead of one of the expected results. In both examples above, we simply treat an error as if we had been signaled to quit.

请注意,如果 WFSO/WFMO 返回错误而不是预期结果之一,则循环执行合理的操作很重要。在上面的两个示例中,我们只是将错误视为已发出退出信号。

You could achieve the same result with the first example by closing the event handle from the main thread, causing the worker thread get an error from WaitForSingleObjectand quit, but I wouldn't recommend that approach.

您可以通过从主线程关闭事件句柄来实现与第一个示例相同的结果,从而导致工作线程从中获取错误WaitForSingleObject并退出,但我不推荐这种方法。

回答by sbi

Since you don't know what the thread is doing, there is no way to safely terminate the thread from outside.

由于您不知道线程在做什么,因此无法从外部安全地终止线程。

Why do you think you cannot terminate it from within?

为什么你认为你不能从内部终止它?

You can create an event prior to starting the thread and pass that event's handle to the thread. You call SetEvent()on that event from the main thread to signal the thread to stop and then WaitForSingleObjecton the thread handle to wait for the thread to actually have finished. Within the threads loop, you call WaitForSingleObject()on the event, specifying a timeout of 0(zero), so that the call returns immediately even if the event is not set. If that call returns WAIT_TIMEOUT, the event is not set, if it returns WAIT_OBJECT_0, it is set. In the latter case you return from the thread function.

您可以在启动线程之前创建一个事件并将该事件的句柄传递给线程。您SetEvent()从主线程调用该事件以通知线程停止,然后调用WaitForSingleObject线程句柄以等待线程实际完成。在线程循环中,您调用WaitForSingleObject()事件,指定超时0(零),这样即使未设置事件,调用也会立即返回。如果该调用返回WAIT_TIMEOUT,则未设置事件,如果返回WAIT_OBJECT_0,则设置事件。在后一种情况下,您从线程函数返回。

I presume your thread isn't just burning CPU cycles in an endless loop, but does some waiting, maybe through calling Sleep(). If so, you can do the sleeping in WaitForSingleObjectinstead, by passing a timeout to it.

我认为您的线程不只是在无限循环中燃烧 CPU 周期,而是进行一些等待,也许是通过调用Sleep(). 如果是这样,您可以WaitForSingleObject通过将超时传递给它来代替睡眠。

回答by Asbj?rn Ulsberg

What are you doing in the background thread? If you're looping over something, you can end the thread within itself by having a shared public static object (like a Boolean) that you set to truefrom the foreground thread and that the background thread checks for and exits cleanly when set to true.

你在后台线程做什么?如果您正在循环执行某些操作,您可以通过将共享公共静态对象(如 a Booleantrue从前台线程设置为并且后台线程在设置为 时检查并干净退出来结束线程true

回答by ZarathustrA

It is a code example for thread management in the fork-join manner. It use struct Thread as a thread descriptor.

是fork-join方式的线程管理代码示例。它使用 struct Thread 作为线程描述符。

Let's introduce some abstraction of the thread descriptor data structure:

下面介绍一下线程描述符数据结构的一些抽象:

    #include <Windows.h>

    struct Thread
    {
        volatile BOOL stop;

        HANDLE event;
        HANDLE thread;
    };

    typedef DWORD ( __stdcall *START_ROUTINE)(struct Thread* self, LPVOID lpThreadParameter);

    struct BootstrapArg
    {
        LPVOID arg;
        START_ROUTINE body;
        struct Thread* self;
    };

Functions for the thread parent use:

线程父级使用的函数:

  • StartThread() initialize this structure and launches new thread.
  • StopThread() initiate thread termination and wait until thread will be actually terminated.

    DWORD __stdcall ThreadBootstrap(LPVOID lpThreadParameter)
    {
        struct BootstrapArg ba = *(struct BootstrapArg*)lpThreadParameter;
    
        free(lpThreadParameter);
    
        return ba.body(ba.self, ba.arg);
    }
    
    VOID StartThread(struct Thread* CONST thread, START_ROUTINE body, LPVOID arg)
    {
        thread->event = CreateEvent(NULL, TRUE, FALSE, NULL);
        thread->stop = FALSE;
        thread->thread = NULL;
    
        if ((thread->event != NULL) && (thread->event != INVALID_HANDLE_VALUE))
        {
            struct BootstrapArg* ba = (struct BootstrapArg*)malloc(sizeof(struct BootstrapArg));
    
            ba->arg = arg;
            ba->body = body;
            ba->self = thread;
    
            thread->thread = CreateThread(NULL, 0, ThreadBootstrap, ba, 0, NULL);
            if ((thread->thread == NULL) || (thread->thread == INVALID_HANDLE_VALUE))
            {
                free(ba);
            }
        }
    }
    
    DWORD StopThread(struct Thread* CONST thread)
    {
        DWORD status = ERROR_INVALID_PARAMETER;
    
        thread->stop = TRUE;
    
        SetEvent(thread->event);
        WaitForSingleObject(thread->thread, INFINITE);
    
        GetExitCodeThread(thread->thread, &status);
        CloseHandle(thread->event);
        CloseHandle(thread->thread);
    
        thread->event = NULL;
        thread->thread = NULL;
    
        return status;
    }
    
  • StartThread() 初始化此结构并启动新线程。
  • StopThread() 启动线程终止并等待线程实际终止。

    DWORD __stdcall ThreadBootstrap(LPVOID lpThreadParameter)
    {
        struct BootstrapArg ba = *(struct BootstrapArg*)lpThreadParameter;
    
        free(lpThreadParameter);
    
        return ba.body(ba.self, ba.arg);
    }
    
    VOID StartThread(struct Thread* CONST thread, START_ROUTINE body, LPVOID arg)
    {
        thread->event = CreateEvent(NULL, TRUE, FALSE, NULL);
        thread->stop = FALSE;
        thread->thread = NULL;
    
        if ((thread->event != NULL) && (thread->event != INVALID_HANDLE_VALUE))
        {
            struct BootstrapArg* ba = (struct BootstrapArg*)malloc(sizeof(struct BootstrapArg));
    
            ba->arg = arg;
            ba->body = body;
            ba->self = thread;
    
            thread->thread = CreateThread(NULL, 0, ThreadBootstrap, ba, 0, NULL);
            if ((thread->thread == NULL) || (thread->thread == INVALID_HANDLE_VALUE))
            {
                free(ba);
            }
        }
    }
    
    DWORD StopThread(struct Thread* CONST thread)
    {
        DWORD status = ERROR_INVALID_PARAMETER;
    
        thread->stop = TRUE;
    
        SetEvent(thread->event);
        WaitForSingleObject(thread->thread, INFINITE);
    
        GetExitCodeThread(thread->thread, &status);
        CloseHandle(thread->event);
        CloseHandle(thread->thread);
    
        thread->event = NULL;
        thread->thread = NULL;
    
        return status;
    }
    

This set of functions is expected to be used from the thread launched by StartThread():

这组函数预计会在 StartThread() 启动的线程中使用:

  • IsThreadStopped() - Check for the termination request. Must be used after waiting on the below functions to identify the actual reason of the termination of waiting state.
  • ThreadSleep() - Replaces use of Sleep() for intra-thread code.
  • ThreadWaitForSingleObject() - Replaces use of WaitForSingleObject() for intra-thread code.
  • ThreadWaitForMultipleObjects() - Replaces use of WaitForMultipleObjects() for intra-thread code.
  • IsThreadStopped() - 检查终止请求。必须在等待以下函数后使用,以识别等待状态终止的实际原因。
  • ThreadSleep() - 代替 Sleep() 用于线程内代码。
  • ThreadWaitForSingleObject() - 替换 WaitForSingleObject() 用于线程内代码。
  • ThreadWaitForMultipleObjects() - 替换 WaitForMultipleObjects() 用于线程内代码。

First function can be used for light-weight checks for termination request during long-running job processing. (For example big file compression). Rest of the functions handle the case of waiting for some system resources, like events, semaphores etc. (For example worker thread waiting new request arriving from the requests queue).

第一个函数可用于在长时间运行的作业处理期间对终止请求进行轻量级检查。(例如大文件压缩)。其余函数处理等待一些系统资源的情况,如事件、信号量等(例如工作线程等待来自请求队列的新请求)。

BOOL IsThreadStopped(struct Thread* CONST thread)
{
    return thread->stop;
}

VOID ThreadSleep(struct Thread* CONST thread, DWORD dwMilliseconds)
{
    WaitForSingleObject(thread->event, dwMilliseconds);
}

DWORD ThreadWaitForSingleObject(struct Thread* CONST thread, HANDLE hHandle, DWORD dwMilliseconds)
{
    HANDLE handles[2] = {hHandle, thread->event};

    return WaitForMultipleObjects(2, handles, FALSE, dwMilliseconds);
}

DWORD ThreadWaitForMultipleObjects(struct Thread* CONST thread, DWORD nCount, CONST HANDLE* lpHandles, DWORD dwMilliseconds)
{
    HANDLE* handles = (HANDLE*)malloc(sizeof(HANDLE) * (nCount + 1U));
    DWORD status;

    memcpy(handles, lpHandles, nCount * sizeof(HANDLE));

    handles[nCount] = thread->event;
    status = WaitForMultipleObjects(2, handles, FALSE, dwMilliseconds);

    free(handles);

    return status;
}