【问题标题】:Stop thread safely安全停止线程
【发布时间】:2012-12-16 03:33:24
【问题描述】:

我写了多线程程序。

我想问TerminateThreadExitThread有什么区别?

这是我收到WM_DESTROY 时的代码 sn-p:

void CleanAll()
{
    DWORD dwExit[MAX_THREAD];
    for(int i = 0; i < MAX_THREAD; i++)
    {
        GetExitCodeThread(hThread[i], &dwExit[i]);
        // I used ExitThread(dwExit[i]); previously
        TerminateThread(hThread[i], dwExit[i]);
        CloseHandle(hThread[i]);
    }
}

我之前使用过ExitThread(),但是我的程序在任务管理器中仍然存在,所以我将其更改为TerminateThread(),我的程序从任务管理器中消失了。

非常感谢任何提前解释。

【问题讨论】:

    标签: c++ multithreading winapi


    【解决方案1】:

    TerminateThread 强制另一个线程退出。你应该不惜一切代价避免调用它,因为它会阻止一个线程死在它的轨道上,而没有任何清理的机会。这包括分配的任何 CRT 内存。

    ExitThread 用于当前运行的线程 干净利落地停止自己。当您在上面调用它时,您可能会强制主 (UI) 线程退出,并且可能让正在运行的线程仍然徘徊。因此,您的程序仍在运行,正如任务管理器中所证明的那样。 GetExitCodeThread 也可能失败,因为线程实际上并未退出。

    但是停止线程的正确方法是通过任何必要的干净方式发出干净的信号,表明它应该退出。 然后让线程自行退出,然后再允许主线程退出。在下面的示例中,我使用了一个全局标志来指示线程它们应该退出。但这假设您的线程将始终有机会轮询全局 bool 状态。另一种更简洁的方法是让每个线程在事件句柄上调用 WaitForSingleObject。当事件句柄发出信号时,线程检查全局变量并在需要时退出。

    bool global_Need_ToExit;  // use a bool or replace with an event handle the thread shoudl wait on
    
    void CleanAll()
    {
        //signal all threads to exit
        global_Need_ToExit = true;
    
        DWORD dwExit[MAX_THREAD];
        for(int i = 0; i < MAX_THREAD; i++)
        {
            // actually wait for the thread to exit
            WaitForSingleObject(hThread[i], WAIT_INFINITE);
    
            // get the thread's exit code (I'm not sure why you need it)
            GetExitCodeThread(hThread[i], &dwExit[i]);
    
            // cleanup the thread
            CloseHandle(hThread[i]);
            hThread[i] = NULL;
        }
    }
    
    DWORD __stdcall YourThreadFunction(void* pData)
    {
    
        while (global_Need_To_Exit == false)
        {
            // do more work
        }
    
        return 0; // same as ExitThread(0);
    }
    

    【讨论】:

    • 出于这个目的,不应该使用非原子类型而不将其声明为volatile。编译器通常会优化循环,因此其他线程可能看不到更新的值。通过声明一个变量volatile,你告诉编译器它应该每次都从它的存储位置重新读取它的值,而不是假设它之前从处理器寄存器或缓存中读取的值。除此之外,很好的答案! +1
    • @Selbie:谢谢 Selbie,你的建议对我很有帮助
    • 对于取消标记,我会使用std::atomic&lt;bool&gt;,而不是使用在不同编译器中具有不同含义的volatile。我认为不需要条件变量。
    • 问题是这样做而不是信号量意味着您的程序只会在所有 sleep() 调用完成后退出 - 如果程序关闭,您不能使用定时等待提前退出.这意味着您不能只是在队列中等待,您必须忙于等待短暂的超时,以便检查全局变量。所以答案是一个好的开始,但不应扩展到非常简单的情况。
    • @AnuragDaware - 一个线程如何向另一个线程发出“需要退出”的信号是开发人员必须解决的设计问题。在我的简单示例中,如果线程“卡住”进行长时间的阻塞操作,那将不起作用。在 Windows 上,大多数阻塞调用通常基于 I/O 或 COM - 两者都有异步操作的场景。需要将其集成到退出条件的轮询模型中。
    【解决方案2】:

    抱歉,投票率最高的答案是使用 ExitThread,因为它会“干净利落地停止自己”。 这是不正确的。该文档指出: ExitThread 是在 C 代码中退出线程的首选方法。但是,在 C++ 代码中,线程在调用任何析构函数或执行任何其他自动清理之前退出。因此,在 C++ 代码中,您应该从您的线程函数中返回。 我听从一个论坛的建议来使用 ExitThread,然后花了几个小时想知道我所有的内存泄漏是从哪里来的,我很难学到这一点。是的,那是针对 C++ 的,但这就是这个问题的意义所在。实际上,即使对于 C,也有一个警告“链接到静态 C 运行时库 (CRT) 的可执行文件中的线程应该使用 _beginthread 和 _endthread 进行线程管理,而不是 CreateThread 和 ExitThread。不这样做会导致线程调用 ExitThread 时出现少量内存泄漏。” 不要在不知道后果的情况下使用 ExitThread!

    【讨论】:

      【解决方案3】:

      你的进程应该在你的线程完成后结束吗?您描述的问题可能有很多解释。如果你想终止整个进程,只需调用ExitProcess

      TerminateThread 的问题在于它很可能导致内存泄漏。它不关心线程状态,也不关心它分配的资源。它也可能导致死锁,具体取决于您进行同步的方式。换句话说,它不会优雅地终止它。

      终止线程的最佳方法是不显式终止它。不要调用TerminateThreadExitThread,而只调用线程函数中的return。您可能需要使用原子标志,或触发事件(或其他同步方法)来发出线程应该终止的信号。然后您的线程应定期检查该标志(或事件)并在返回(终止)之前释放所有资源。

      【讨论】:

      • 感谢jweyrich的回复,我的问题已经解决了,我按照Selbie的指示,正如你所说,它只是线程函数的返回。
      【解决方案4】:

      您可以使用 SingleWaitObjects 或 QueueUserAPC,但您必须确保线程停止检查这些异步对象或等待一个结束并正常终止线程。在 linux 中您可以使用信号

      【讨论】:

      • 感谢您的回复 CMAF-CMAC。我的问题现在没有了。我只是听从塞尔比的指示。
      【解决方案5】:

      最终,您需要像查看迷你进程一样查看每个线程(无论如何,这就是它们的底层内容)。为了让它干净地关闭,你需要额外的基础设施让你——主线程——告诉它停止。

      实现这一点的最简单方法是信号系统,POSIX 的发明者很早就发现了这种系统,因此直接在操作系统中公开。这种系统在现实世界中最好的例子是美国邮局。每个代理都有一个唯一的地址,他们可以从该地址发送和接收来自其他地址的邮件(信号),并且由代理决定当他们收到信号时做什么。有些是信息或垃圾,因此会被忽略,而另一些则是高优先级,如果您忽略它们,后果自负。

      在代码级别,您需要以下内容才能使其正常工作(我已经为图像处理程序构建了其中一些):
      1)一些抽象的“事件”或“控制信号”对象。如果足够,请随意使用默认的 Windows 事件或 linux 信号。
      2)某种“管道”:操作系统通常会有一个字面内核原语调用一个管道,让一个端拉数据,而另一个端将数据放入。这个家伙通常对于进程中的通信来说是矫枉过正的,但是你的对象需求在概念上是等价的。
      3) 线程中的代码从管道中检索信号并对其进行操作。 4) 所有线程都识别的“停止”或“取消”信号。

      当然,您可以使用 TerminateThread 硬中止线程,就像您可以使用 taskkill 或 CTRL-C 终止进程一样。 ExitThread 是一种特定于 Windows 的方式来构建干净的关闭信号,就像我在 (4) 中描述的那样,但是您仍然需要 while-loop & "WaitForSingleObject" (3) 以及特定于 Windows 的句柄 (2) 才能使其工作。

      【讨论】:

        猜你喜欢
        • 2014-07-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-20
        • 1970-01-01
        • 2011-05-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多