我在具有 8GB 内存的 Fedora 35 系统上遇到与 C++ 内存管理相关的问题。具体来说,我正在使用两个线程,每个线程应该分配大约 4GB 内存,使用它,然后释放它。线程由
std::mutex
控制,以确保一次只有一个线程分配或释放内存。
这是我正在使用的代码:
#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;
}
我预计该程序的总内存使用量不会超过大约 4GB(加上程序和线程的开销),因为一个线程应该在另一个线程开始分配之前释放其内存。然而,我观察到内存使用量逐渐增加,直到系统耗尽内存并杀死进程。
我知道在 C++ 中,释放的内存不一定会立即返回到操作系统,并且可能会保留下来以供同一程序将来分配。但是,在这种情况下,第二个线程似乎没有重用第一个线程的内存,这导致内存使用过多。
如果您对这个问题有任何见解,我将不胜感激。为什么内存没有按预期释放?有没有办法确保内存不再需要后立即释放?
根据评论中的讨论,这是我最好的猜测:
您正在使用 glibc 的
malloc
实现,这是字符串的内存分配请求最终结束的地方。 (glibc 是 Linux 发行版上最常见的 C 标准库实现提供者,但还有其他如 musl)
您所看到的行为是这个特定的
malloc
实现方式的副作用。特别是,它不认为 80000
字节足够大,无法使用 mmap
完全单独地为每个字符串分配和释放内存。默认限制为 128k,可以在代码中设置(使用不可移植的 mallopt
)以及环境变量 MALLOC_MMAP_THRESHOLD_
。
因此,使用用于所有较小分配的通常的基于块的 arena 分配器。默认情况下,
malloc
将使用多个arena,直到达到某个上限,并尝试为不同的线程分配不同的arena,这样它们的分配就不会干扰。
此外,
malloc
实现会延迟在free
之后释放竞技场使用的堆顶部的空闲内存,直到稍后的时间点,例如调用 malloc
,以避免释放然后立即重新获取内存,也许还可以保持 free
尽可能快。
因此,在线程中关闭
}
后,列表似乎已通过调用 glibc 的 free
完全释放,但 free
决定尚未将内存释放回系统。
因为另一个线程随后使用自己的堆在自己的竞技场中进行操作,所以它也不会将第一个线程的内存释放回操作系统,并且最终所需的内存量是预期的两倍。
您可以通过调用
malloc
,强制 }
实现在
malloc_trim(0)
之后将所有内存释放回操作系统。当然,这是不可移植的,只能在使用 glibc 或兼容替代方案的系统上运行。
我对glibc的
malloc
实现不太了解,所以上面的解释可能有错误的地方。有关实现的概述,请访问 https://sourceware.org/glibc/wiki/MallocInternals。