线程分配内存,主进程死亡,会发生什么?

3

我正在检查一些代码是否存在内存泄漏的问题,然后我想到了这种可能性。基本上我所做的伪代码如下:

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()中的所有清理都不会发生。
在我看来,最奇怪的部分是,当我去掉WaitForMultipleObjects()时,我的代码就可以正常工作! 所有的清理工作都进行了,包括thread_func()内部的清理。请问有人能帮我理解这个问题吗?
请注意,我目前只在一个点检查threadTerminated。稍后我会在其他重要点上检查。我这样做只是想确认自己是否理解发生了什么。
再次感谢! 你的回答非常有帮助。

存在不同类型的内存泄漏,如果一个泄漏并没有增长,它通常只是一个小麻烦而不是真正的问题,因为程序终止后泄漏也不会随着时间的推移而变大。 - AndersK
@claptrap,说得好!但我想消除我发现的任何内存泄漏情况。 - Anish Ramaswamy
如果 DLL 的清理函数仅在进程退出时调用,则可以不必销毁关键部分。就像建筑物要被拆除一样,你不需要打扫地板!如果需要处理卸载 DLL 但进程将继续运行的情况,则需要根据现有答案将清理与线程同步。请记住,即使没有关键部分,也不能安全地在线程仍在从中运行代码时卸载 DLL。 - Harry Johnston
2个回答

4
当一个进程终止时,操作系统会释放它所分配的所有内存,所以不调用free来释放分配的fileName不会引起任何问题。
无论如何,我会按照以下方式更改代码:
  1. 定义一个标志来指示线程是否应该终止:bool terminated;
  2. 当进程即将终止时,将terminated设置为true,并且等待线程终止
  3. 在线程函数中,在重要点检查terminated(例如,在每个循环的条件检查中)。如果terminatedtrue,则停止线程完成的所有操作(例如,停止循环),释放资源(例如,释放线程分配的内存),然后返回。
  4. 在线程终止之后(也就是,线程函数返回之后),进程可以释放所有剩余的资源(例如,释放进程分配的内存、删除关键部分等),然后退出。
这样你就可以避免在线程终止之前删除关键部分,并且可以释放每个已分配的资源。

1
我不建议使用bool。相反,使用事件,这样线程可以休眠而不会产生无意义的CPU负载。请参见我的答案。 - Lundin
感谢您的回答!这绝对是我问题可能的解决方案。@Lundin,为什么您不建议使用bool?只是好奇。 - Anish Ramaswamy
@Lundin,为什么while(!my_bool) do_work();会占用100%的CPU?如果my_boolFALSE,那么while循环会退出,对吧?或者我完全错了? - Anish Ramaswamy
@Lundin 我可以为你展示几乎每个API函数、编程范式、语言等的类似文本。 - kol
@kol 再次,您没有提供任何理由。为什么使用Sleep在循环中轮询bool比使用WaitForSingleObject更好?在VCL TThread的情况下,如果Terminate()成员函数不产生事件,我会非常惊讶,然后在底层实现中捕获它,然后将其转换为bool以方便使用。 VCL和Delphi/Builder的整个业务理念是为程序员隐藏所有API调用。不要假设它提供的接口等同于底层实现。 - Lundin
显示剩余12条评论

1
  • 你的线程应该具有某种循环形式才有意义。
  • 在使用线程时,您需要发明一些方法以安全、可预测的方式优雅地终止它们。
  • 临界区是粗糙的,请用互斥对象替换它们,线程可以等待互斥对象。

正确的设计方式应该是这样的:

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

编辑:

请记住,创建和删除线程会产生大量的开销代码,并可能减慢程序的运行速度。除非它们是工作线程,在这种情况下,工作量与开销相比显著,否则请考虑在整个程序的生命周期内保持线程处于睡眠状态而不是创建和删除线程。


1
我强烈推荐这篇优秀的阅读材料。 - Lundin
谢谢你的回答!这绝对是一个比我现在的设计好得多的方案。但是,WAIT_OBJECT_0是什么意思呢?另外一个问题,你为什么说临界区很笨重呢?还有,那个链接看起来非常有趣。非常感谢你! - Anish Ramaswamy
@AnishRam 你需要在MSDN上详细查找所有这些API函数。WAIT_OBJECT_0在WaitForMultipleObjects()中有解释。至于互斥量与临界区的区别,我远非专家,所以可能会有错误,但我认为互斥量可以与WaitForMultipleObjects函数一起使用,而临界区在EnterCriticalSection中内置了等待,因此无法有效地与“kill event”等待结合使用。 - Lundin
我必须说,你发布的那个链接是一篇非常棒的文章。非常感谢。 - Anish Ramaswamy
@AnishRam 我认为性能是一个无意义的争论。PC不是实时操作系统,当你使用WaitFor和类似的函数时,你会把时间片让给其他进程。在缓慢的Windows系统上进行上下文切换的时间不是以微秒表示的,如果它保持<10ms,你可能会非常高兴。我还强烈质疑任何用纳秒精度在Windows上进行基准测试的人,那完全是胡说八道。现代Windows PC非常慢,就是这样。 - Lundin
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接