为何Valgrind会在该C++实现中报告内存泄漏?
为什么你的std::thread + fork代码会触发Valgrind内存泄漏?
让我们拆解一下问题的核心——你的代码里的嵌套fork操作和std::thread的交互是导致内存泄漏的关键,和exit()本身关系不大。
问题根源分析
当你在第一次fork出的子进程里创建了std::thread后,紧接着又调用了一次fork():
- 首先,
std::thread底层依赖系统线程库(比如pthreads),创建线程时会分配一些内部管理资源(比如线程控制块)。 - 当你调用
fork()时,操作系统会复制当前进程的地址空间,但只会保留当前正在执行fork的线程——其他线程不会被复制到子进程中。 - 这就导致第二次fork出的子进程里,虽然有
uploader这个std::unique_ptr<std::thread>对象,但是它指向的std::thread对应的实际系统线程根本不存在于这个子进程里。 - 当这个子进程调用
exit(0)时,它无法正确清理std::thread的内部资源——因为这些资源对应的线程在子进程里从未启动过,但内存已经被分配了,Valgrind就会把这标记为确定的内存泄漏。
哪怕父进程(第一次fork的子进程)后来join了线程,也无济于事——因为第二次fork的子进程已经带着这个“残缺”的std::thread对象退出了,没机会释放它的资源。
验证结论的小测试
你可以试试把第二次fork的代码注释掉,运行后Valgrind的泄漏报告就会消失——这说明问题确实出在嵌套fork和std::thread的组合上。
修复方案
针对你的代码,有几个可行的修复方向:
- 避免嵌套fork:如果可以,不要在创建了std::thread的进程里再次fork。把第二次fork的逻辑放到独立的进程中,或者调整任务流程。
- 在fork前清理线程资源:在调用第二次fork之前,确保
uploader已经被join,并且重置指针(比如uploader.reset()),这样子进程就不会继承这个未完成的线程对象。 - 在子进程中重置线程对象:在第二次fork出的子进程里,第一时间重置
uploader指针,让unique_ptr释放对应的std::thread资源,再执行后续逻辑。
比如修改后的代码片段:
int child_id = fork(); if (!child_id){ uploader.reset(); // 子进程不需要这个线程,先释放资源 // Do something exit(0); }
你的原始代码
#include <thread> #include <unistd.h> #include <sys/wait.h> #include <memory> using namespace std; static void upload(const string url, const string path){ int sync_status; } int main(){ bool flag = true; for(int worker_id = 0; worker_id < 1; worker_id++){ int worker_pid = fork(); if (worker_pid == 0){ while (true){ std::unique_ptr<std::thread> uploader(nullptr); if (flag){ // This block only occurs once inside while loop flag = false; string path = "l", url = "k"; // Valgrind reports the next line uploader = std::make_unique<std::thread>(upload, url, path); } int child_id = fork(); if (!child_id){ // Do something exit(0); } waitpid(child_id, NULL, 0); if(uploader && uploader->joinable()){ uploader->join(); } } // Do something exit(0); } } return 0; }
Valgrind报告
==16424== 80 bytes in 1 blocks are definitely lost in loss record 2 of 2 ==16424== at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==16424== by 0x10AA39: std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> > std::thread::_S_make_state<std::thread::_Invoker<std::tuple<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >(std::thread::_Invoker<std::tuple<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&&) (thread:197) ==16424== by 0x10A04A: std::thread::thread<void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) (thread:126) ==16424== by 0x109A64: std::_MakeUniq<std::thread>::__single_object std::make_unique<std::thread, void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) (unique_ptr.h:825) ==16424== by 0x109514: main (main.cpp:21)
内容的提问来源于stack exchange,提问作者Shikhar Jaiswal
相关产品推荐
相关产品推荐

