【问题标题】:Terminating a thread gracefully not using TerminateThread()不使用 TerminateThread() 优雅地终止线程
【发布时间】:2009-11-09 16:35:04
【问题描述】:

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

请,这里需要代码。

【问题讨论】:

    标签: c windows multithreading


    【解决方案1】:

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

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

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

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

    更新:

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

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

    另一个用途是按需做一些事情。基本相同,但超时设置为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;
        }
    }
    

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

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

    【讨论】:

    • 线程回调函数是一个 for(;;) 就是这样。没有什么不寻常的。所以,我将重复这个问题,使用 WaitForSingleObject() 和 SetEvent() 我怎样才能向我的线程发出我需要完成的信号?如果我调用 SetEvet(FinishEvent) WaitForSingleObject() 会返回什么? 0? 1? -1?
    • @Uri 当您调用SetEvent 时,WaitForSingleObject 将返回WAIT_OBJECT_0(即0)。当您提到使用WaitForSingleObject 时,我认为这意味着您已经在使用该事件来向工作线程发出信号以执行某些操作(除了退出)。另请注意,在某些情况下,您需要通知 wotker 线程退出,然后等待它实际停止,然后再继续。 (为此,您可以在线程句柄上使用WaitForSingleObject)。
    • +1 这是我应该给出的答案。这样做了很多次。
    • 您链接的 MSKB 文章表明它仅适用于 MSVC 6.0 - 现代版本仍然会发生这种泄漏吗?
    • 现代版本的 C 运行时不会出现内存泄漏,除非 以下条件都为真: C 运行时是静态链接的,而不是作为 DLL 链接的;并且该应用程序在 Windows XP 或更早版本上运行。 (这是因为应用了两种不同的解决方法,一种基于 DllMain,另一种基于 FlsCallback。)
    【解决方案2】:

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

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

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

    我认为您的线程不仅在无限循环中消耗 CPU 周期,而且还进行了一些等待,可能是通过调用 Sleep()。如果是这样,您可以改为在 WaitForSingleObject 中休眠,方法是将超时传递给它。

    【讨论】:

    • 线程没有做任何事情,只是检查一个特定的程序是否一直在运行。
    【解决方案3】:

    你在后台线程做什么?如果你正在循环某些东西,你可以通过共享一个公共静态对象(如Boolean)来结束线程本身,你从前台线程设置为true,并且后台线程检查并干净地退出当设置为true时。

    【讨论】:

      【解决方案4】:

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

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

          #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;
          };
      

      线程父使用的函数:

      • 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;
        }
        

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

      • IsThreadStopped() - 检查终止请求。必须在等待以下函数后使用,以确定等待状态终止的实际原因。
      • ThreadSleep() - 将 Sleep() 替换为线程内代码。
      • ThreadWaitForSingleObject() - 将 WaitForSingleObject() 替换为线程内代码。
      • ThreadWaitForMultipleObjects() - 将 WaitForMultipleObjects() 用于线程内代码。

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

      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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-14
        • 2015-06-07
        • 2012-03-16
        • 2021-05-28
        • 2019-11-10
        • 2016-05-31
        相关资源
        最近更新 更多