02-----linux下多线程程序占用虚拟内存非常高

一 业务场景分析

因为多线程在平时是非常常见的,最近有点空,想自己写个线程池而不是用别人写好的。
所以开撸,发现当我在调试一个线程池时,发现使用了一两个小时后,虚拟内存占用得非常高。然后我开始分析,一开始我先写了一个不带调整线程的线程池,发现线程池开启几个小时后,虚拟内存都是很稳定,基本也就正常的几百m。而当我添加了调整线程后,发现同样一个小时多后,虚拟内存变得非常的大,达到20g左右。
注:这里调整线程的作用是:可以根据任务数的大小自动调整线程池中线程的数量,所以就涉及线程的添加和销毁。

1 测试

经过上面发现问题后,肯定想排查问题,于是开启测试。
这是我测试的情况:

  1. 这是添加调整线程的情况,可以看到经过一两个小时后,虚拟内存都变得非常的大。
    在这里插入图片描述
    在这里插入图片描述
    下面是C语言写的带调整线程的线程池,情况和上面C++的一样。
    在这里插入图片描述
    在这里插入图片描述

  2. 然后我就把调整线程的代码去掉,发现果然情况变得不一样,经过类似的时间,虚拟内存仍然是一样的,非常稳定。
    在这里插入图片描述
    在这里插入图片描述

2 写测试代码

由于线程池添加了调整线程这些内容,测起来不方便,于是就自己写个程序测试。

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void *thread1(void *arg)
{
    printf("thread1\n");

    return NULL;//创建线程但不回收
}

int main(){

    getchar();
    printf("ok\n");
    pthread_t tid1;

    while (getchar())
    {
        printf("start create thread.\n");
        pthread_create(&tid1, NULL, thread1, NULL);
    }
    

    return 0;
}
  1. 首先运行程序
./main
  1. 然后查看该进程的pid。我的是22651。
ps -ajx | grep main

在这里插入图片描述

  1. 使用top观察虚拟内存。一开始是15428,大概15M左右。
top -p 22651

在这里插入图片描述

  1. 然后查看开辟一个线程所占用的堆栈大小。
ulimit -a		//查看stack size那一行
或者
ulimit -s

下面看到是8m。
在这里插入图片描述
单位需要从ulimit -a查看,单位是kb。
在这里插入图片描述

  1. 好了,目前我们可以开始调程序观察了。
    1)下面看到,由于我不小心按到其它键,创建了两个线程。然后查看VirMem大小,大小为31820,刚好和15428 + 2*8192=31812差不多相等。
    在这里插入图片描述
    在这里插入图片描述

2)然后再创建线程,按下enter即可。同样看到VirMem=40016,刚好和31812 + 8192=40004差不多相等。
在这里插入图片描述
在这里插入图片描述

3)然后再创建线程,按下enter即可。同样看到VirMem=48212,刚好和40004 + 8192=48196差不多相等。
在这里插入图片描述
在这里插入图片描述

4)然后你不断重复创建线程,情况都和上面一样。所以我们可以分析程序了。

  1. 程序可以看到,当我们创建线程后,并未对线程进行回收,而是直接return了。所以我们只需要证明调用join回收后,观察虚拟内存的情况,若未增加,就验证了我们的猜想,是没有回收导致的,否则就可能需要从其它方面入手。

  2. 更改代码,添加一个调整线程去回收退出后的线程,观察情况。
    先列出代码:

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <vector>
using namespace std;

vector<pthread_t> garbage;//垃圾回收,记录退出线程的tid,以便join回收
pthread_mutex_t mymutex;//锁住垃圾队列

void *thread1(void *arg)
{

    printf("thread1\n");

    pthread_t t = pthread_self();
    pthread_mutex_lock(&mymutex);
    garbage.push_back(t);
    pthread_mutex_unlock(&mymutex);

    return NULL;//创建线程但不回收
}

//调整线程,回收资源
void *adjust(void *arg)
{
    while(true)
    {
        sleep(2);//定时回收

        pthread_mutex_lock(&mymutex);
        int size = garbage.size();
        vector<pthread_t>::iterator it = garbage.begin();
        if(garbage.size() >= 0){
            for(it; it != garbage.end(); it++)
            {
                pthread_join(*it, NULL);
            }
            garbage.clear();
        }

        pthread_mutex_unlock(&mymutex);
        printf("清理完本次退出的线程,个数为=%d\n", size);
    }

    pthread_exit(NULL);
}

int main(){

    pthread_mutex_init(&mymutex, NULL);

    //先创建一个调整线程,用于定时回收资源
    pthread_t adjust_tid;
    pthread_create(&adjust_tid, NULL, adjust, NULL);

    getchar();
    printf("ok\n");
    pthread_t tid1;

    while (getchar())
    {
        printf("start create thread.\n");
        pthread_create(&tid1, NULL, thread1, NULL);
    }
    
    pthread_mutex_destroy(&mymutex);

    return 0;
}

同理,安装上面一开始的情况,去top -p pid去观察创建线程后的Virt。
我的情况是,当虚拟内存到达一定情况后,一般是几百M,和你程序初始有关,我这里到达187m左右,无论你开辟多少线程,过了2s内被回收后,最终还是变回187m。也就是说,Virt的大小始终保持稳定。

我的测试情况:
1)下图是我创建一定线程并回收后,最终稳定的情况值。
在这里插入图片描述
2)然后我们再程序的界面上,不断的按下enter,main主线程就会创建多个线程,此时top的Virt可以看到秒增加变大。
在这里插入图片描述

3)然后回收后,立马回到187m。
下图由于比较难截图,只能使用windows自带的截图,比较模糊但是还是能看的,回收的线程数是36。
在这里插入图片描述

所以由上面的测试结果可以看到,导致虚拟内存变高的原因是我们线程结束后,未使用join进行回收。

实际上,我们测试时可以使用detach去代替的。情况和上面类似,只不过最终稳定的值比上面低点,大概24m左右。

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <vector>
using namespace std;


void *thread1(void *arg)
{
    pthread_detach(pthread_self());

    printf("thread1\n");

    return NULL;//这里等价于pthread_exit();
}


int main(){

    getchar();
    printf("ok\n");
    pthread_t tid1;

    while (getchar())
    {
        printf("start create thread.\n");
        pthread_create(&tid1, NULL, thread1, NULL);
    }

    return 0;
}

3 总结

虚拟内存非常高,导致的情况可能是非常多的,我这里是因为添加调整线程后,部分退出的线程pthread_exit退出后,未调用join进行回收,导致在虚拟内存占用高,但是在实际的物理内存中,它的值是正常的。
虽然部分场景下,只有物理内存维持正常不爆增,虚拟内存过高还是可以让程序正常进行的,但是程序员必须防范于未然,而且看着也难受,既然找到bug,后续就自己动手改代码咯。

关于其它出现虚拟内存占用高的情况的文章:
为什么linux下多线程程序如此消耗虚拟内存

LinuxC 中可以使用多线程来实现一个简单的 HTTP 服务器,并支持虚拟目录功能。以下是基本的实现思路: 1. 创建一个监听套接字,等待客户端连接; 2. 当客户端连接时,创建一个新的线程来处理该客户端的请求; 3. 在新线程中,解析客户端的 HTTP 请求,并根据请求的 URL 获取要访问的文件路径; 4. 如果文件路径是一个目录,则返回该目录下的文件列表; 5. 如果文件路径是一个文件,则读取文件内容并返回给客户端。 具体的实现细节可以参考以下步骤: 1. 使用 socket 函数创建一个监听套接字; 2. 使用 bind 函数将监听套接字绑定到指定的 IP 地址和端口号; 3. 使用 listen 函数开启监听模式; 4. 使用 accept 函数接受客户端连接,并创建一个新的线程来处理客户端请求; 5. 在新线程中,使用 recv 函数接收客户端的 HTTP 请求; 6. 解析 HTTP 请求中的 URL,获取要访问的文件路径; 7. 如果文件路径是一个目录,则使用 opendir 和 readdir 函数获取目录下的文件列表,并将文件列表返回给客户端; 8. 如果文件路径是一个文件,则使用 fopen 和 fread 函数读取文件内容,并将文件内容返回给客户端; 9. 使用 send 函数将响应数据发送给客户端; 10. 使用 close 函数关闭套接字和文件句柄。 以上是一个基本的多线程 HTTP 服务器实现思路,可以根据具体需求进行调整和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值