关于POSIX pthreads、虚拟内存和brk()函数的问题
Problem with POSIX pthreads and virtual memory
参考链接:https://www.unix.com/programming/76183-problem-posix-pthreads-virtual-memory.html
准备工作
这部分不是要探讨的问题本身,只是做编译准备工作时要解决的问题。
问题1:编译时报错 iostream: 没有那个文件或目录
user@zh-ubuntu:~/Work/2024-2$ gcc brk_test.c
brk_test.c:2:10: fatal error: iostream: 没有那个文件或目录
2 | #include <iostream>
| ^~~~~~~~~~
解决方法:https://blog.csdn.net/xiaoqixiaoguai/article/details/128051365
注意路径中的“9”要根据实际值修改(测试时的值是11)。
问题2:error: ‘sleep’ was not declared in this scope
解决方法:#include <unistd.h> 即可。
程序解释
下面是ChatGPT对于程序代码的解释(代码见参考链接):
程序首先包含输入输出操作所需的头文件、输入输出流类和用于多线程的 pthread(POSIX 线程)库。
程序定义了一个以 void 指针为参数并返回 void 指针的函数 cliente。cliente 函数分配一个内存块(100,000 字节),休眠 15 秒,释放分配的内存,为指针赋值 NULL,然后使用 pthread_exit 退出线程。
在主函数中,它会初始化线程属性 tattr 并设置其堆栈大小。它还将属性的分离状态设置为 PTHREAD_CREATE_JOINABLE,这意味着主线程可以加入(join)并等待单个线程完成。
然后初始化一个整数变量 ret,并计算堆栈的大小。
然后,程序创建一个包含 401 个 pthreads mythread[401] 的数组,并进入一个循环,在循环中使用 pthread_create 创建线程,并传递 cliente 函数作为线程的启动例程。如果线程创建成功,则使用 pthread_detach 将创建的线程分离。
循环将继续创建线程并分离它们,直到 ret 的值达到 400。创建线程后,程序进入一个什么也不做的无限循环(while(true) {}),这实际上是暂停主线程。
代码的总体功能是创建大量线程(本例中为 400 个),每个线程都将执行 cliente 函数,该函数分配大量内存块,休眠 15 秒,释放内存,然后退出线程。
验证过程
编译、运行程序,程序持续运行、不退出(因为主线程进入空循环)。可以通过top指令发现,程序占用4145096KB的虚拟内存,且保持此值不变,即使经过相当长的时间,占用内存大小仍没有变化。这表明程序分配的内存在线程结束后没有被释放。

下面,在cliente函数中增加printf("End Thread \r\n",);,以便观察子线程的结束。为了方便,子线程数量设置为10:

窗口1展示了代码,在子线程结束之前输出End Thread;
窗口2显示,子线程在经过15s延时之后输出了信息,但进程没有结束;
窗口3显示,分配给进程的虚拟内存值自始至终没有发生改变。
结论
原帖中的问题描述和解答如下:
this code uses aprox. 370 Mb of virtual memory, But when the threads finish it does not liberate the above mentioned memory.
That is because the kernel allocated 370MB of memory to the process - once a process gets memory it keeps that memory allocated until the process ends. This is because calls to brk() to allocate memory are very expensive, and brk() would be called to 'give back' the memory as well as to allocate it.
下面是ChatGPT的解释:
线程结束后,内存不会立即被取消分配,这与内核内存管理的性质有关。当进程分配内存时,内核分配内存的方式是在释放内存时不立即去分配内存。这是因为进程将来可能需要再次分配内存,而频繁地去分配和分配内存会耗费系统资源和性能。
内存的分配和取消分配可能涉及到 brk() 和 sbrk() 等系统调用。这些系统调用操作进程的数据段,用于动态调整分配给进程的内存量。然而,这些操作涉及到开销,为了尽量减少开销,内核可能会选择保留分配给进程的内存,即使内存被释放后也是如此。
因此,即使线程已结束,内存已释放,内核也不会立即取消分配给进程的内存。相反,内核会保留分配给进程的内存,直到进程结束。这种方法有助于提高内存管理的效率,避免频繁地分配和取消分配内存,因为这样会耗费大量系统资源。
备注
原帖4楼,将main函数中的if (error==0)一句替换为如下,结果相同:
if(error==0) pthread_detach(mythread[ret]);
原帖7楼,修改了cliente函数,但测试int id=pthread_self();不成功,报错段错误 (核心已转储)。

浙公网安备 33010602011771号