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

一 业务场景分析

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

1 测试

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

  1. 这是添加调整线程的情况,可以看到经过一两个小时后,虚拟内存都变得非常的大。
    02-----linux下多线程程序占用虚拟内存非常高_第1张图片
    02-----linux下多线程程序占用虚拟内存非常高_第2张图片
    下面是C语言写的带调整线程的线程池,情况和上面C++的一样。
    02-----linux下多线程程序占用虚拟内存非常高_第3张图片
    02-----linux下多线程程序占用虚拟内存非常高_第4张图片

  2. 然后我就把调整线程的代码去掉,发现果然情况变得不一样,经过类似的时间,虚拟内存仍然是一样的,非常稳定。
    02-----linux下多线程程序占用虚拟内存非常高_第5张图片
    02-----linux下多线程程序占用虚拟内存非常高_第6张图片

2 写测试代码

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

#include 
#include 
#include 
#include 
#include 

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

02-----linux下多线程程序占用虚拟内存非常高_第7张图片

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

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

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

2)然后再创建线程,按下enter即可。同样看到VirMem=40016,刚好和31812 + 8192=40004差不多相等。
02-----linux下多线程程序占用虚拟内存非常高_第9张图片
在这里插入图片描述

3)然后再创建线程,按下enter即可。同样看到VirMem=48212,刚好和40004 + 8192=48196差不多相等。
02-----linux下多线程程序占用虚拟内存非常高_第10张图片
在这里插入图片描述

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

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

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

#include 
#include 
#include 
#include 
#include 
#include 
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)下图是我创建一定线程并回收后,最终稳定的情况值。
02-----linux下多线程程序占用虚拟内存非常高_第11张图片
2)然后我们再程序的界面上,不断的按下enter,main主线程就会创建多个线程,此时top的Virt可以看到秒增加变大。
02-----linux下多线程程序占用虚拟内存非常高_第12张图片

3)然后回收后,立马回到187m。
下图由于比较难截图,只能使用windows自带的截图,比较模糊但是还是能看的,回收的线程数是36。
02-----linux下多线程程序占用虚拟内存非常高_第13张图片

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

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

#include 
#include 
#include 
#include 
#include 
#include 
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下多线程程序如此消耗虚拟内存

你可能感兴趣的:(GDB调试,相关命令及环境处理,多线程,linux)