You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

堆上持续创建pthread的实现与Valgrind内存分析方法

引言

我所开发的程序会创建子线程,希望使用Valgrind memcheck工具对其开展内存分析。根据此前提出的相关问题下的回复,为了通过Valgrind memcheck获得可靠的测试与分析结果,必须使用可合并(joinable)线程,而非分离(detached)线程。

栈分配与堆分配选型

我的程序规模较大,无法在线程创建的同一作用域内完成线程join操作,因此选择在堆上为pthread_t类型变量分配存储空间。

尝试方案1:创建后立即join

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <stdint.h>

void my_thread() {
  printf("I'm in a child thread!\n");
  pthread_exit(NULL);
}

pthread_t* const make_thread() {
  pthread* const thread = malloc(sizeof(pthread_t));
  pthread_create(thread, NULL, (void*) &my_thread, NULL));
  return thread;
}

int main() {
  printf("Hello, world!\n");
  uint8_t i;
  for(i = 0; i < 255; ++i) {
    pthread_t* const thread_handle = make_thread();
    pthread_join(*thread_handle, NULL);
    free(thread_handle);
  }
  return 0;
}

该方案逻辑可正常运行,但我希望对示例进行扩展:不在线程创建后立即执行join,仅在程序退出时统一执行join(例如业务场景中线程可能为长生命周期线程),换言之上述串行join的方案无法发挥多线程的并行优势,违背多线程设计初衷。我的目标是创建线程后,仅在程序退出时强制执行join操作。

尝试方案2:程序退出前统一join

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <stdint.h>
#include <glib-2.0/glib.h>
#include <unistd.h>

void my_thread() {
  sleep(3);
  printf("I'm in a child thread!\n");
  pthread_exit(NULL);
}

pthread_t* const make_thread() {
  pthread* const thread = malloc(sizeof(pthread_t));
  pthread_create(thread, NULL, (void*) &my_thread, NULL));
  return thread;
}

int main() {
  printf("Hello, world!\n");

  GArray* const thread_handles = g_array_new(TRUE, TRUE, sizeof(pthread*));

  // 核心业务循环
  uint8_t i;
  for(i = 0; i < 255; ++i) {
    pthread_t* const thread_handle = make_thread();
    g_array_append_val(thread_handles, thread_handle);
  }

  for(i = 0; i < thread_handles->len; ++i) {
    pthread_t* const thread_handle =
        g_array_index(thread_handles, pthread*, i);
    pthread_join(*thread_handle, NULL);
    free(thread_handle);
  }
  g_array_free(thread_handles, TRUE);

  return 0;
}

该方案可正常运行,但存在一个待解决的问题:如果上述核心业务循环为无限循环,要如何避免thread_handles数组持续扩张直至耗尽所有可用内存?在我的实际业务程序中(上述代码为最小复现示例),程序会持续接收网络消息,针对特定类型的网络消息启动对应处理线程。现寻求可靠的实现方案,支持在堆上持续创建可join的pthread,兼容Valgrind memcheck内存检测,同时避免无限运行场景下的线程句柄存储内存膨胀问题,实现规范的线程生命周期管理,支撑程序的内存与性能分析。


解决方案

核心思路是定期回收已退出线程的句柄资源,不需要把所有线程句柄都留存到程序退出阶段,只要保证每个joinable线程最终被pthread_join回收、对应堆内存被释放即可,完全兼容Valgrind检测要求,也不会出现内存无限膨胀的问题。

实现方式

  • 维护一个线程句柄存储容器(你当前用的GArray即可,也可以用链表、队列结构)
  • 每次创建新线程往容器追加句柄后,立刻遍历扫描整个容器:
    • 对每个句柄调用pthread_tryjoin_np(这是glibc提供的非阻塞join接口,若线程已退出则立刻完成回收,未退出则直接返回EBUSY不阻塞)
    • 对成功join的线程,直接释放对应堆上分配的pthread_t内存,把该位置从容器中移除
  • 程序收到退出信号、准备终止前,再对容器中剩余的未退出线程执行阻塞式pthread_join,等待所有线程执行完成后释放对应资源、销毁容器。

关键注意点

  • pthread_tryjoin_np是GNU扩展接口,在POSIX标准中没有定义,如果你需要跨POSIX平台兼容,可以额外启动一个低优先级的回收线程,专门对容器中的线程句柄执行阻塞式pthread_join,回收完成后释放内存、从容器删除对应条目即可,效果完全一致。
  • 对容器的读写操作需要加互斥锁,避免业务线程追加句柄、回收线程扫描删除条目时出现竞态条件。
  • 不需要担心扫描容器的性能开销:容器中留存的只会是还在运行的线程句柄,已经退出的线程会被及时清理,容器长度永远等于当前运行中的线程总数,不会无限增长;每次扫描的时间复杂度和当前运行线程数成正比,对业务逻辑的性能影响可以忽略。
  • 这种实现方式完全满足Valgrind memcheck的检测要求:所有joinable线程都被显式join,所有堆分配的pthread_t内存都被显式释放,不会出现"possibly lost"的假阳性误报。

补充说明:分离线程虽然不需要手动join,但是线程退出时其资源的回收时机是由内核调度决定的,Valgrind在程序退出时可能会检测到还未完全回收的线程栈、TLS结构内存,报出假阳性的内存泄漏错误,这也是必须用joinable线程做内存检测的核心原因,上述方案完全规避了这个问题。


内容的提问来源于stack exchange,提问作者karobar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 13:15:38