Fedora 35下C++多线程大列表内存释放异常问题咨询
在8GB内存的Fedora 35系统中运行C++程序时遇到内存管理问题:两个线程通过std::mutex互斥,确保同一时间只有一个线程进行内存分配/释放,每个线程需分配约4GB内存,使用后释放。
预期程序总内存占用不会超过4GB(含程序及线程开销),但实际内存占用持续增长,直到系统耗尽内存终止进程。已知C++释放的内存可能不会立即归还操作系统,会留存供程序后续复用,但此处第一个线程释放的内存未被第二个线程复用,导致内存占用过高。
疑问:为何内存未按预期释放?如何确保内存不再需要后立即释放?
相关代码:
#include <thread> #include <list> #include <string> #include <mutex> std::mutex mtx; // Function for first thread void manageList1() { mtx.lock(); { std::list<std::string> myList1; for(int i = 0; i < 50000; ++i) { myList1.push_back(std::string(80000, 'a')); // Approx. 4GB } myList1.clear(); // Clear list } mtx.unlock(); while(true){} // Keep thread 1 from exiting } // Function for second thread void manageList2() { mtx.lock(); { std::list<std::string> myList2; for(int i = 0; i < 50000; ++i) { myList2.push_back(std::string(80000, 'a')); // Approx. 4GB } myList2.clear(); // Clear list } mtx.unlock(); } int main() { std::thread listThread1(manageList1); std::thread listThread2(manageList2); // Don't join listThread1 listThread2.join(); return 0; }
一、内存未复用的核心原因
- 线程本地内存缓存的隔离:
现代堆分配器(比如Fedora默认的glibc ptmalloc2)会为每个线程创建独立的线程本地缓存(TLC)。第一个线程分配的4GB内存会存入自身的TLC,释放时内存只会回到该线程的TLC中,而非全局堆或操作系统。第二个线程作为独立线程,无法访问其他线程的TLC,只能从全局堆或操作系统申请新内存,导致总内存占用叠加。 - 线程存活导致缓存无法回收:
第一个线程在释放内存后进入死循环,线程始终存活,其TLC会一直保留占用的内存,既无法被其他线程复用,也不会归还给操作系统。
二、解决办法
1. 让线程自然退出,释放本地缓存
移除第一个线程的死循环,让线程在完成内存释放后自然退出。线程退出时,其TLC会被销毁,内存会归还给全局堆,后续其他线程可以复用这些内存。
修改后的manageList1:
void manageList1() { mtx.lock(); { std::list<std::string> myList1; for(int i = 0; i < 50000; ++i) { myList1.push_back(std::string(80000, 'a')); } myList1.clear(); } mtx.unlock(); // 移除死循环,线程自动退出 }
同时修改main函数,等待两个线程完成:
int main() { std::thread listThread1(manageList1); std::thread listThread2(manageList2); listThread1.join(); listThread2.join(); return 0; }
2. 强制归还内存给操作系统
如果必须保持第一个线程存活,可以调用堆分配器的接口,强制将线程本地缓存中未使用的内存归还给操作系统。对于glibc,使用malloc_trim(0)函数:
#include <malloc.h> // 需包含此头文件 void manageList1() { mtx.lock(); { std::list<std::string> myList1; for(int i = 0; i < 50000; ++i) { myList1.push_back(std::string(80000, 'a')); } myList1.clear(); } mtx.unlock(); malloc_trim(0); // 强制将未使用内存归还操作系统 while(true){} }
注意:malloc_trim是glibc特有的接口,其他平台需使用对应替代(如Windows的HeapCompact)。
3. 禁用线程本地缓存
通过设置环境变量调整glibc的堆分配器行为,禁用线程本地缓存,让所有线程共享全局内存池:
export MALLOC_ARENA_MAX=1
运行程序时先执行上述命令,这样释放的内存会回到全局堆,可被其他线程复用。但此方法会降低多线程内存分配的性能,因为所有线程的分配操作需要竞争全局锁。
4. 使用自定义分配器
为std::list和std::string实现不依赖线程本地缓存的自定义分配器,让内存直接从全局堆分配和释放。此方法适合对内存控制有严格要求的场景,但实现复杂度较高。
内容的提问来源于Stack Exchange,提问作者Mehdi At

