【问题标题】:Thread allocates memory, main process dies, what happens?线程分配内存,主进程死亡,会发生什么?
【发布时间】:2013-02-13 14:44:19
【问题描述】:

我目前正在检查我的一些代码是否存在内存泄漏,这种可能性让我很震惊。基本上我正在做的伪代码如下,

void thread_func()
{
    char *fileName = malloc(someSize);
    /* Do something with fileName and other things */
    /* Enter a critical section */
    modify some global variables
    /*Exit critical section */
    free(fileName);
    return;
}

此函数位于 DLL 中。临界区和其他东西由同样驻留在同一个 DLL 中的函数初始化。

现在,我的主进程(这是一个 GUI)有一个取消按钮。当用户单击该按钮时,我调用 DLL 的清理函数,该函数恰好破坏了我的关键部分。

我发现如果用户在thread_func()执行过程中点击取消,thread_func()会继续执行。当它到达临界区代码时,临界区是无效的,所以我就退出了。这就是我在线程内检查取消事件的方式(因为在执行thread_func() 期间,我的应用程序中没有其他东西可以调用DLL 的清理)。

当我发现临界区无效时,我无法在thread_func() 中释放fileName。我的猜测是因为thread_func() 在主进程退出后失去了对fileName 的访问权限。我的猜测对吗?我的主要问题是,如果在这种情况下我不释放 fileName,我是否会面临内存泄漏的风险?

我已经搜索了很多相关信息,但到目前为止还没有找到任何东西。如果有人能指出正确的方向/回答我的问题,我会非常高兴。

谢谢!

编辑:

我决定根据kol的建议做一些初步测试(见下面的答案)。我注意到一些我无法理解的非常奇怪的东西。现在我的代码如下:

void thread_func()
{
    char *fileName = malloc(someSize);
    /* Do something with fileName and other things */

    if(threadTerminated)
    {
        /* Cleanup */
        return;
    }

    /* Enter a critical section */
    modify some global variables
    /*Exit critical section */
    free(fileName);
    return;
}

在我的 GUI 中,我的 OnCancel 事件处理程序类似于:

void OnCancel()
{
    threadTerminated = TRUE;
    WaitForMultipleObjects(noOfRunningThreads, threadHandles, TRUE, INFINITE);

    /* Other cleanup code */
}

我注意到WaitForMultipleObjects() 无限期挂起,我的 GUI 变得无响应。 WaitForMultipleObjects()不应该快点回来吗?此外,如果 threadTerminatedTRUE,则不会在 thread_func() 中进行任何清理。

这是 IMO 最奇怪的部分。当我删除WaitForMultipleObjects() 时,我的代码运行良好!所有的清理工作都会发生,包括thread_func() 内部的清理工作。有人可以帮我理解一下吗?

请注意,我目前只检查threadTerminated。稍后我将在其他重要点检查它。我这样做只是为了看看我是否明白发生了什么。

再次感谢!您的回答非常有帮助。

【问题讨论】:

  • 有不同类型的内存泄漏,在泄漏没有增长的情况下,它通常比实际问题更不方便,因为程序无论如何都会终止并且泄漏不会随着时间。
  • @claptrap,好点子!但我想删除我发现的任何内存泄漏情况。
  • 如果 DLL 的清理函数只在进程退出时被调用,根本就不要费心破坏临界区。楼房拆了,不用扫地了!如果您需要处理正在卸载 DLL 但进程将继续进行的情况,那么您需要根据现有答案将清理与线程同步。请记住,即使没有关键部分,您也无法在线程仍在从中运行代码时安全地卸载 DLL。

标签: c windows memory-leaks


【解决方案1】:

当进程终止时,操作系统将释放它分配的所有内存,因此不在分配的fileName 上调用free 不会导致任何问题。

无论如何,我会按以下方式更改代码:

  1. 定义一个指示线程是否应该终止的标志:bool terminated;
  2. 当进程即将终止时,将terminated设置为truewait for the thread to terminate
  3. 在线程函数中,在重要点检查terminated(例如,在每个循环的条件检查中)。如果terminatedtrue,则停止线程所做的一切(例如,停止循环),释放资源(例如,线程分配的空闲内存),然后返回。
  4. 线程终止后(即线程函数返回后),进程可以释放所有剩余资源(例如,进程分配的空闲内存、删除临界区等)并退出。

这样您可以避免在线程终止之前删除关键部分,并且可以释放所有分配的资源。

【讨论】:

  • 我不推荐布尔值。请改用事件,以便线程可以休眠而不会产生毫无意义的 CPU 负载。看我的回答。
  • @Lundin 我不确定某个活动是否更适合此目的。我的意思是没有必要等待这个标志。线程完成它的工作,并在决策点检查标志以确定它是否可以继续或应该以优雅的方式终止。 (Delphi 的设计者使用了相同的逻辑:TThread 类有一个 Terminated 属性,它是一个布尔值。)
  • 感谢您的回答!这绝对似乎是我的问题的可能解决方案。 @Lundin,你为什么不推荐一个布尔值?只是好奇。
  • 很难说什么最适合这种特殊情况。以while(!my_bool) do_work(); 实现的线程将消耗 100% 的 CPU。 link 中的更多信息我在另一条评论中提出。
  • @Lundin,为什么while(!my_bool) do_work(); 会消耗 100% 的 CPU?如果my_boolFALSE,while 会退出对吗?还是我完全错过了什么?
【解决方案2】:
  • 您的线程可能应该有某种形式的循环才有意义。
  • 使用线程时,您需要发明一些方法以安全、可预测的方式优雅地终止它们。
  • 关键部分是直截了当的,用线程可以等待的互斥对象替换它们。

设计它的正确方法是这样的:

HANDLE h_event_killthread = CreateEvent(...);
HANDLE h_mutex = CreateMutex(...);

...

void thread_func()
{
  const HANDLE h_array [] = 
  { 
    h_event_killthread,
    h_mutex 
  };

  ... // malloc etc

  bool time_to_die = false;

  while(!time_to_die)
  {
    DWORD wait_result;
    wait_result = WaitForMultipleObjects(2,         // wait for 2 handles
                                         h_array,   // in this array
                                         FALSE,     // wait for any handle
                                         INFINITE); // wait forever

    if(wait_result == WAIT_OBJECT_0) // h_event_killthread
    {
      time_to_die = true;
    }
    else if(wait_result == (WAIT_OBJECT_0+1)) //h_mutex
    {
      // we have the mutex
      // modify globals here
      ReleaseMutex(h_mutex);

      // do any other work that needs to be done, if meaningful
    }
  }

  cleanup();
}


// and then in the GUI:

void cancel_button ()
{
  ...
  SetEvent(h_event_killthread);
  WaitForSingleObject(the_thread, INFINITE);
  ...
}

编辑:

请记住,创建和删除线程会产生大量开销代码,并且可能会减慢程序速度。除非它们是工作线程,与开销相比,工作量很大,否则请考虑在程序的整个生命周期内保持线程处于活动状态但处于休眠状态。

【讨论】:

  • 我强烈推荐this 优秀的阅读。
  • 感谢您的回答!这绝对是一个比我拥有的更好的设计。 WAIT_OBJECT_0 是什么?另一个问题,你为什么说关键部分是生硬的?此外,该链接看起来很有趣。非常感谢!
  • 哦,别管WAIT_OBJECT_0,用谷歌搜索。另一件事,目前我的应用程序只有一小部分是多线程的,它要求所有线程同时启动(不是我的决定),否则我可能会考虑你的挂起线程建议。
  • @AnishRam 您必须在MSDN 上详细查找所有这些 API 函数。 WAIT_OBJECT_0 在下面 WaitForMultipleObjects() 中解释。至于互斥锁与临界区,我远非专家,所以我可能错了,但我相信互斥锁可以与 WaitForMultipleObjects 函数一起使用,而临界区在 EnterCriticalSection 中内置了等待,因此不能与“kill event”等待有效结合。
  • 我使用EnterCriticalSection而不是互斥锁的全部原因是this链接。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-16
  • 1970-01-01
  • 1970-01-01
  • 2018-02-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多