Fedora 35上带线程的大列表中的C++内存释放

kh212irz  于 2023-06-25  发布在  其他
关注(0)|答案(2)|浏览(171)

我遇到了一个与内存管理有关的问题,在C++的Fedora 35系统与8GB的内存。具体来说,我使用两个线程,每个线程分配大约4GB的内存,使用它,然后释放它。线程由std::mutex控制,以确保一次只有一个线程在分配或释放内存。
下面是我使用的代码:

  1. #include <thread>
  2. #include <list>
  3. #include <string>
  4. #include <mutex>
  5. std::mutex mtx;
  6. // Function for first thread
  7. void manageList1() {
  8. mtx.lock();
  9. {
  10. std::list<std::string> myList1;
  11. for(int i = 0; i < 50000; ++i) {
  12. myList1.push_back(std::string(80000, 'a')); // Approx. 4GB
  13. }
  14. myList1.clear(); // Clear list
  15. }
  16. mtx.unlock();
  17. while(true){} // Keep thread 1 from exiting
  18. }
  19. // Function for second thread
  20. void manageList2() {
  21. mtx.lock();
  22. {
  23. std::list<std::string> myList2;
  24. for(int i = 0; i < 50000; ++i) {
  25. myList2.push_back(std::string(80000, 'a')); // Approx. 4GB
  26. }
  27. myList2.clear(); // Clear list
  28. }
  29. mtx.unlock();
  30. }
  31. int main() {
  32. std::thread listThread1(manageList1);
  33. std::thread listThread2(manageList2);
  34. // Don't join listThread1
  35. listThread2.join();
  36. return 0;
  37. }

我预计这个程序的总内存使用量不会超过大约4GB(加上程序和线程的开销),因为一个线程应该在另一个线程开始分配内存之前释放它的内存。然而,我观察到的是,内存使用量逐渐增加,直到系统耗尽内存并杀死进程。
我知道在C++中,释放的内存不一定立即返回给操作系统,它可能会被同一个程序保留下来供将来分配。然而,在这种情况下,第一个线程的内存似乎没有被第二个线程重用,这会导致过多的内存使用。
我将感谢任何对这个问题的见解。为什么没有按预期释放内存?有没有一种方法可以确保内存在不再需要时立即释放?
谢谢你的时间和帮助。

p5cysglq

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

nkoocmlb

nkoocmlb2#

答案在评论中给你,你深深地陷入了未定义的行为领域。然而,如果它有任何帮助,在MSVC中,程序的行为与您期望的一样:

然后它当然会崩溃,因为你没有加入或分离第一个线程。

相关问题