apr_thread使用内存之谜

网友投稿 276 2022-11-30

apr_thread使用内存之谜

问题起因

问题的起因是因为使用了一个apr的服务,产生了巨大的virtual memory,具体的表现是,在top中可以看到该进程的VIRT和RES,VIRT比实际上使用的要大很多。

在google上找到如下文章

​​have noticed that my multithreaded APR program consumes a *very*large amount of virtual memory per thread and I can't think of areason why. I am running Debian 3.1 (latest kernel 2.6.8-3-686) and Itried the test program below with both the APR 0.9.x that comes withDebian and with the latest APR 1.2.x version.我注意到多线程的APR程序每一个线程会消耗很大的virtual memory,我没有想明白是为什么。我使用的是Debian 3.1的系统,基于apr版本 APR 0.9.x和 APR 1.2.xIn both cases I end up with around 800 MB of virtual RAM for 100 threads:20385 ivanr 16 0 802m 956 1800 S 0.0 0.4 0:00.02 test在如下两个例子中,运行100个线程大致使用了800MBvirtual RAMAm I doing something wrong or is this a bug? Any help is greatly appreciated!void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {apr_thread_exit(thread, 0);}int main(int argc, const char * const argv[]) {apr_pool_t *pool;int i;apr_app_initialize(&argc, &argv, NULL);atexit(apr_terminate);apr_pool_create(&pool, NULL);for(i = 0; i < 100; i++) {apr_thread_t *thread = NULL;apr_thread_create(&thread, NULL, thread_worker, NULL, pool);}apr_sleep(1000 * 1000 * 1000);}

VIRT & RES & SHR

如果你执行top看一下进程的内存占用,会得到如下几列

VIRT 它是程序“需要”内存的总和,包含了加载的动态库内存,与其他进程共享的内存,以及分配给他的内存; 但是如果程序申请了100m但是仅仅使用了10m,这里还是按照100m计算统计RES 它是程序正在使用内存的总和,一个主要区别是如果程序申请了100m但是仅仅使用了10m,这里是按照10m计算统计SHR 加载的库的内存,如果仅仅是使用库里的一些函数,但是整个库还是会加载入内存。

一个“空白”程序,如下,它会占用多少VIRT,RES,SHR?

int main(int argc, const char * const argv[]) { return 0;}

它占用了4164kB的VIRT, 368KB的RES, 268KB的SHR。SHR基本上为动态库加载。一个“什么都没有的"c程序,基础的链接是这样的

那么程序”自身“多大呢? RES-SHR = 368- 268 = 100KB

程序总共”需要“的大小 VIRT = 4164kB

如果我们申请1m内存再释放它,是否可以跟上述程序暂用内存一致呢? 来试一下

int main(int argc, const char * const argv[]) { char *p = (char *)calloc(1048576, sizeof(char)); free(p); // break 1 return 0; // break 2}

这里我们增加2个断点,来观察内存使用的变化;

break1

与上面”空白“程序相比,此时RES是增加1k; 但是VIRT也增长了1k

break2

与上面”空白“程序相比,VIRT是回到了原点。 RES仍有所增加,但是可以理解,毕竟多了几行代码,code也是要占用空间的; SHR也增加了一点(这个是为什么?)

换成malloc呢?

int main(int argc, const char * const argv[]) { char *p; for (int i=0; i<1048576; i++) p = malloc(sizeof(char)); //free(p); return 0;}

在不释放内存的情况下,RES可以涨到33M;VIRT涨到37M

在释放后,内存情况大体也相当; 说明多次调用malloc还是有一定开销的。(这里换成calloc也一样)

int main(int argc, const char * const argv[]) { char *p; for (int i=0; i<1048576; i++) { p = malloc(sizeof(char)); free(p); } return 0;}

apr_thread测试

1. 使用样例代码(不做thread_join)

上面的帖子跟遇到的情况有些类似,所以我把他的样例程序跑了一下

#include #include #include #define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {apr_thread_exit(thread, 0);}int main(int argc, const char * const argv[]) { apr_pool_t *pool; int i; apr_status_t status = 0; apr_app_initialize(&argc, &argv, NULL); atexit(apr_terminate); apr_pool_create(&pool, NULL); apr_thread_t *thread[THREAD_NUM]; for(i = 0; i < THREAD_NUM; i++) { apr_thread_create(&thread[i], NULL, thread_worker, NULL, pool); } apr_sleep(1000 * 1000 * 1000);}

样例中运行100个apr_thread

这个是样例代码的结果。

可以看到RES仅仅1.7M,但是VIRT确实906M,将近一个G了。这个差距也太大了。确实有文章里说的问题。

2. 执行thread_join

如果将所有线程join,如下

#define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {apr_thread_exit(thread, 0);}int main(int argc, const char * const argv[]) { apr_pool_t *pool; int i; apr_status_t status = 0; apr_app_initialize(&argc, &argv, NULL); atexit(apr_terminate); apr_pool_create(&pool, NULL); apr_thread_t *thread[THREAD_NUM]; for(i = 0; i < THREAD_NUM; i++) { apr_thread_create(&thread[i], NULL, thread_worker, NULL, pool); } // for(i = 0; i < THREAD_NUM; i++) { apr_thread_join(&status, thread[i]); } apr_sleep(1000 * 1000 * 1000); return 0;}

RES1.3M,几乎差不多;VIRT120M,比不join少了很多;但是仍有近100倍差距

3. 再做apr_pool_clear

如果在thread_join后面再调用apr_pool_clear(pool),主动clear一下pool呢?

几乎没有变化。

4. 移除apr_thread_exit

既然最后都有thread_join,把线程中的apr_thread_exit移除试试呢?

代码如下

#define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {//apr_thread_exit(thread, 0);return NULL;}int main(int argc, const char * const argv[]) { apr_pool_t *pool; int i; apr_status_t status = 0; apr_app_initialize(&argc, &argv, NULL); atexit(apr_terminate); apr_pool_create(&pool, NULL); apr_thread_t *thread[THREAD_NUM]; for(i = 0; i < THREAD_NUM; i++) { apr_thread_create(&thread[i], NULL, thread_worker, NULL, pool); } for(i = 0; i < THREAD_NUM; i++) { apr_thread_join(&status, thread[i]); } apr_sleep(1000 * 1000 * 1000); return 0;}

对比发现;RES和SHR几乎没有变化;但是VIRT居然只有52M,比之前少了一半还多。

这么看来,如果最后有join的动作,还是不要执行apr_thread_exit的比较节省内存。

5. 将线程detach运行

#define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {//apr_thread_exit(thread, 0);return NULL;}int main(int argc, const char * const argv[]) { apr_pool_t *pool; int i; apr_status_t status = 0; apr_threadattr_t *thread_attr; apr_app_initialize(&argc, &argv, NULL); atexit(apr_terminate); apr_pool_create(&pool, NULL); apr_threadattr_create(&thread_attr, pool); apr_threadattr_detach_set(thread_attr, 1); apr_thread_t *thread[THREAD_NUM]; for(i = 0; i < THREAD_NUM; i++) { assert(apr_thread_create(&thread[i], thread_attr, thread_worker, NULL, pool)==APR_SUCCESS); } apr_sleep(1000 * 1000 * 1000); return 0;}

与joinable的thread几乎一样。

如果还原apr_thread_exit,也跟joinable的thread几乎一样

所以detach对内存几乎没有影响。

6. 限制thread的栈大小

#define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {//apr_thread_exit(thread, 0);return NULL;}int main(int argc, const char * const argv[]) { apr_pool_t *pool; int i; apr_status_t status = 0; apr_threadattr_t *thread_attr; apr_app_initialize(&argc, &argv, NULL); atexit(apr_terminate); apr_pool_create(&pool, NULL); apr_threadattr_create(&thread_attr, pool); //apr_threadattr_detach_set(thread_attr, 1); apr_threadattr_stacksize_set(thread_attr,10240); //10k apr_thread_t *thread[THREAD_NUM]; for(i = 0; i < THREAD_NUM; i++) { assert(apr_thread_create(&thread[i], thread_attr, thread_worker, NULL, pool)==APR_SUCCESS); } for(i = 0; i < THREAD_NUM; i++) { apr_thread_join(&status, thread[i]); } apr_sleep(1000 * 1000 * 1000); return 0;}

每一个线程的栈大小限制为10k,好像没有什么变化??

每个thread的栈默认是多少呢? 一般来说,默认为8M,即8,388,608bytes

可以通过ulimit -s或者ulimit -a看一下

可以通过**apr_threadattr_stacksize_set(thread_attr,8388608);**来还原一下默认值,跑出来结果跟不设置差不多,VIRT为52M;

好像真没有什么变化。

pthread测试

作为对比,我们同样的使用pthread来做一轮测试

1. 不做pthread_join

#include #include #include #define THREAD_NUM 100static void *thread_worker(void *data) { pthread_exit(NULL); return NULL;}int main(int argc, const char *const argv[]) { int i = 0; pthread_t thread[THREAD_NUM]; for (i = 0; i < THREAD_NUM; i++) { assert(pthread_create(&thread[i], NULL, thread_worker, NULL) == 0); } sleep(1000000); return 0;}

与apr_thread对比,VIRT893M 基本相当; RES 1.2M比之前的1.7M了一点;SHR 0.4M也小了一点;

注释掉 pthread_exit的结果如下:

VIRT826M,RES 1.1M,SHR 0.4M

做pthread_join

static void *thread_worker(void *data) { pthread_exit(NULL); return NULL;}int main(int argc, const char *const argv[]) { int i = 0; pthread_t thread[THREAD_NUM]; for (i = 0; i < THREAD_NUM; i++) { assert(pthread_create(&thread[i], NULL, thread_worker, NULL) == 0); } for (i = 0; i < THREAD_NUM; i++) { pthread_join(thread[i], NULL); } sleep(1000000); return 0;}

相比apr_thread,VIRT 106M/ RES 0.7M/SHR 0.5M 都小一些

3. 去除pthread_exit

#define THREAD_NUM 100static void *thread_worker(void *data) { //pthread_exit(NULL); return NULL;}int main(int argc, const char *const argv[]) { int i = 0; pthread_t thread[THREAD_NUM]; for (i = 0; i < THREAD_NUM; i++) { assert(pthread_create(&thread[i], NULL, thread_worker, NULL) == 0); } for (i = 0; i < THREAD_NUM; i++) { pthread_join(thread[i], NULL); } sleep(1000000); return 0;}

5. 限制stacksize

#define THREAD_NUM 100static void *thread_worker(void *data) { //pthread_exit(NULL); return NULL;}int main(int argc, const char *const argv[]) { int i = 0; int stacksize = 10240; pthread_t thread[THREAD_NUM]; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, stacksize); for (i = 0; i < THREAD_NUM; i++) { assert(pthread_create(&thread[i], &attr, thread_worker, NULL) == 0); } pthread_attr_destroy(&attr); sleep(1000000); return 0;}

相对于不设置大小,内存各项指标基本不变。(同apr_thread结果)

6. 结论

pthread的内存占用略比apr_thead少,但是整体都出现VIRT比RES大几百倍的情况; 尤其是pthread_exit的使用,似乎对VIRT的参数影响较大。

优化解决方案

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:极智开发 | 讲解 React 组件三大属性之二:props
下一篇:@Valid 无法校验List&lt;E&gt;的问题
相关文章

 发表评论

暂时没有评论,来抢沙发吧~