我遇到了一个与内存管理有关的问题,在C++的Fedora 35系统与8GB的内存。具体来说,我使用两个线程,每个线程分配大约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++中,释放的内存不一定立即返回给操作系统,它可能会被同一个程序保留下来供将来分配。然而,在这种情况下,第一个线程的内存似乎没有被第二个线程重用,这会导致过多的内存使用。
我将感谢任何对这个问题的见解。为什么没有按预期释放内存?有没有一种方法可以确保内存在不再需要时立即释放?
谢谢你的时间和帮助。
2条答案
按热度按时间p5cysglq1#
根据评论中的讨论,这是我最好的猜测:
您使用的是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_trim(0)
强制malloc
实现在}
之后将所有内存释放回操作系统。当然,这是不可移植的,只能在使用glibc或兼容替代品的系统上工作。我对glibc的
malloc
实现了解不多,所以上面的解释可能有些错误。有关实现的概述,请参见https://sourceware.org/glibc/wiki/MallocInternals。nkoocmlb2#
答案在评论中给你,你深深地陷入了未定义的行为领域。然而,如果它有任何帮助,在MSVC中,程序的行为与您期望的一样:
然后它当然会崩溃,因为你没有加入或分离第一个线程。