【问题标题】:pthread_exit vs. returnpthread_exit 与返回
【发布时间】:2011-04-20 04:04:26
【问题描述】:

我有一个可连接的 pthread runner 函数,定义如下:

void *sumOfProducts(void *param)
{
...
pthread_exit(0);
}

这个线程应该加入主线程。

每当我通过 Valgrind 运行我的程序时,我都会得到以下漏洞

LEAK SUMMARY:
   definitely lost: 0 bytes in 0 blocks
   indirectly lost: 0 bytes in 0 blocks
     possibly lost: 0 bytes in 0 blocks
   still reachable: 968 bytes in 5 blocks
        suppressed: 0 bytes in 0 blocks

ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 10)

我查看了 pthreads 的手册页,上面写着:

  The new thread terminates in one of the following ways:

   * It  calls  pthread_exit(3),  specifying  an exit status value that is
     available  to  another  thread  in  the  same  process   that   calls
     pthread_join(3).

   * It  returns  from  start_routine().   This  is  equivalent to calling
     pthread_exit(3) with the value supplied in the return statement.

   * It is canceled (see pthread_cancel(3)).

   * Any of the threads in the process calls exit(3), or the  main  thread
     performs  a  return  from main().  This causes the termination of all
     threads in the process.

奇迹般地,当我用 return 语句替换 pthread_exit() 时,泄漏消失了

return(NULL);

我的实际问题是三管齐下的:

  1. 谁能解释为什么 return 语句没有泄漏?
  2. 在退出线程方面,这两个语句之间是否存在一些根本区别?
  3. 如果是这样,什么时候应该优先选择一个?

【问题讨论】:

  • 你真的在使用 C++ 吗? C++ 使用作用域来销毁对象并返回将“离开”该作用域,而 pthread_exit 不会。
  • 很抱歉,我的问题中从未提及 C++。到目前为止,我正在用 C 语言做所有事情。
  • 我知道你没有提到它,但这是一个猜测,这就是我问的原因。 :) 你能提供一个完整的testcase吗?

标签: c linux pthreads valgrind


【解决方案1】:

Valgrind 很难跟踪 pthread_exit 变量,这就是为什么使用 pthread_exit 时它会显示内存泄漏且标记仍可访问;但在 return 的情况下则不然。

【讨论】:

    【解决方案2】:

    看起来调用exit()(显然,pthread_exit())会留下自动分配的变量。您必须返回或投掷才能正确放松。

    C++ valgrind possible leaks on STL string:

    @Klaim:我看不出该文件在哪里说我错了,但如果 它确实是错误的。引用 C++ 标准(§18.3/8): “调用 exit() 不会破坏自动对象。” – 詹姆斯·麦克内利斯 2010 年 9 月 10 日 19:11

    由于执行“return 0”而不是“pthread_exit(0)”似乎可以解决您的问题(以及我的问题......谢谢),我假设两者之间的行为相似。

    【讨论】:

      【解决方案3】:

      不确定您是否仍然对此感兴趣,但我目前正在调试类似的情况。使用pthread_exit 的线程会导致 valgrind 报告可达块。原因似乎在这里得到了很好的解释:

      https://bugzilla.redhat.com/show_bug.cgi?id=483821

      基本上看来pthread_exit 会导致dlopen 在进程退出时从未明确清除。

      【讨论】:

        【解决方案4】:

        我的经验是 valgrind 难以跟踪为可连接线程的状态分配的存储空间。 (这与 caf 指示的方向相同。)

        由于您似乎总是返回0 的值,我猜您可能需要从应用程序的角度加入您的线程?如果是这样考虑从一开始就分离启动它们,这可以避免分配该内存。

        缺点是你要么有:

        1. main 的结尾。如果你知道 预先线程数,a 简单的静态分配 pthread_barrier 可以。
        2. 或退出你main pthread_exit 这样你就不会 杀死其余正在运行的线程 这可能还没有完成。

        【讨论】:

          【解决方案5】:

          以下最小测试用例展示了您描述的行为:

          #include <pthread.h>
          #include <unistd.h>
          
          void *app1(void *x)
          {
              sleep(1);
              pthread_exit(0);
          }
          
          int main()
          {
              pthread_t t1;
          
              pthread_create(&t1, NULL, app1, NULL);
              pthread_join(t1, NULL);
          
              return 0;
          }
          

          valgrind --leak-check=full --show-reachable=yes 显示由pthread_exit() 调用的函数分配的 5 个块,这些块未被释放但在进程退出时仍可访问。如果pthread_exit(0);替换为return 0;,则5个块不分配。

          但是,如果您测试创建和加入大量线程,您会发现退出时使用的未释放内存量没有增加。这以及它仍然可以访问的事实表明您只是看到了 glibc 实现的奇怪之处。几个 glibc 函数在第一次被调用时使用 malloc() 分配内存,它们在进程生命周期的剩余时间内一直分配内存。 glibc 不会费心在进程退出时释放此内存,因为它知道该进程无论如何都会被拆除 - 这只是浪费 CPU 周期。

          【讨论】:

          • 有没有办法强制释放 glibc 分配的内存?并不是说它是 nessescary,但我想我在这个网站的某个地方看到过它......
          • @Christoffer:Valgrind 从大约 1.1 版开始,调用了一个名为 __libc_freeres 的函数,它应该这样做。然而,有一些版本的 glibc 在这个函数中有错误(因为它通常不会被调用,除非程序在内存调试器下运行)。它通常被默认调用,除非 valgrind 使用参数 --run-libc-freeres=no 运行。
          • 谢谢!这让我发疯了。我认为这是 glibc 的怪事,但我不确定。
          【解决方案6】:

          您是否真的在使用 C++?澄清一下 - 您的源文件以 .c 扩展名结尾,而您正在用 gcc 编译它,而不是 g++

          您的函数似乎很可能正在分配您希望在函数返回时自动清理的资源。像 std::vectorstd::string 这样的本地 C++ 对象执行此操作,如果您调用 pthread_exit,它们的析构函数可能不会运行,但如果您只是返回,它们会被清除。

          我的偏好是避免使用低级 API,例如 pthread_exit,并且尽可能从线程函数返回。它们是等价的,除了 pthread_exit 是一个事实上的流控制结构,它绕过了您正在使用的语言,但 return 没有。

          【讨论】:

          • 您正在使用gcc 进行编译,它或它调用的任何代码不可能意外使用C++ 功能?
          • 放心,我是用 gcc 编译的。回答前请看标签。
          • @crypto:我确实查看了标签。人们是否在使用纯 C 编译器,或者他们是否使用类似于 C 的 C++ 子集,并不总是很清楚。无论如何,caf 的回答可能更相关。
          猜你喜欢
          • 2013-12-04
          • 1970-01-01
          • 2012-06-11
          • 2023-03-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-11
          • 2012-01-20
          相关资源
          最近更新 更多