子线程退出导致的内存泄漏

问题描述: 

         程序开始时正常创建(pthread_create)子线程用于接收数据包,而主线程进行发送数据包请求,主线程请求完成两秒之后,通知子线程退出(pthread_cancel + pthread_join),子线程执行清理函数(pthread_cleanup_push)之后退出。通过top和ps观察,程序每次重复上述步骤后,程序占用的内存比例必定增加。

问题原因:

        “pthread_cancel ”被调用后,如果目标线程在某个取消点,那么它会立即响应取消请求并调用所有注册的清理处理程序,并且在清理处理程序执行完成后立即终止子线程,而我写的程序在子线程return之前才会释放动态申请的内存,因此导致了内存泄漏的发生。

测试代码:

        以下面代码举例,子线程(thread_test_func)在退出的时候做的free(pnum),但通过打印的信息发现,子线程并没有执行到free就退出了,所以pnum的内存并没有释放掉。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <byteswap.h>
#include <sys/types.h>

static void thread_test_cleanup(void *arg)
{
        uint32_t *pnum = (uint32_t *)arg;
        printf("thread test cleanup, num[0]=%u\n", pnum[0]);
        printf("thread test cleanup, num[1]=%u\n", pnum[1]);
        printf("thread test cleanup, num[2]=%u\n", pnum[2]);
        printf("thread test cleanup, return\n");
        return;
}

static void *thread_test_func(void *arg)
{
        uint32_t *pnum = calloc(3, sizeof(uint32_t));
        if (pnum == NULL)
        {
                printf("thread test calloc error: %s\n", strerror(errno));
                exit(-1);
        }

        pnum[0] = 10;
        pnum[1] = 20;
        pnum[2] = 30;

        // 以下两处不设置, 均使用默认
        // pthread_setcancelstate(); 默认值: PTHREAD_CANCEL_ENABLE
        // pthread_setcanceltype(); 默认值: PTHREAD_CANCEL_DEFERRED
        pthread_cleanup_push(thread_test_cleanup, pnum);

        while (1)
        {
                sleep(1);
                printf("thread test func sleep\n");
        }

        printf("thread test func free\n");
        free(pnum);

        pthread_cleanup_pop(0);
        return NULL;
}

int main (int argc, char *argv[])
{
        pthread_t threadid;

        if (pthread_create(&threadid, NULL, (void *)thread_test_func, NULL) != 0)
        {
                printf("thread create error: %s\n", strerror(errno));
                exit(-1);
        }

        sleep(3);

        printf("thread main(cancel): start\n");
        pthread_cancel(threadid);
        printf("thread main(cancel): finish\n");

        printf("thread main(join): start\n");
	    pthread_join(threadid, NULL);
        printf("thread main(join): finish\n");
        return 0;
}

/* 程序执行结果:
 * thread test func sleep
 * thread test func sleep
 * thread main(cancel): start
 * thread main(cancel): finish
 * thread main(join): start
 * thread test cleanup, num[0]=10
 * thread test cleanup, num[1]=20
 * thread test cleanup, num[2]=30
 * thread test cleanup, return
 * thread main(join): finish
 */

解决方法:

        再注册一个cleanup清理函数,专门用于动态内存的释放,但是要注意注册的先后顺序,在pthread_cleanup_pop的时候,是采用后注册先执行的原则,所以如果要释放的内存在其他清理函数中使用到了,则需要严格注意注册顺序。修改后的代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <byteswap.h>
#include <sys/types.h>

static void thread_test_cleanup_free(void *arg)
{
        free(arg);
        printf("thread test cleanup free pnum\n");
        return;
}

static void thread_test_cleanup(void *arg)
{
        uint32_t *pnum = (uint32_t *)arg;
        printf("thread test cleanup, num[0]=%u\n", pnum[0]);
        printf("thread test cleanup, num[1]=%u\n", pnum[1]);
        printf("thread test cleanup, num[2]=%u\n", pnum[2]);
        printf("thread test cleanup, return\n");
        return;
}

static void *thread_test_func(void *arg)
{
        uint32_t *pnum = calloc(3, sizeof(uint32_t));
        if (pnum == NULL)
        {
                printf("thread test calloc error: %s\n", strerror(errno));
                exit(-1);
        }

        pnum[0] = 10;
        pnum[1] = 20;
        pnum[2] = 30;

        // 以下两处不设置, 均使用默认
        // pthread_setcancelstate(); 默认值: PTHREAD_CANCEL_ENABLE
        // pthread_setcanceltype(); 默认值: PTHREAD_CANCEL_DEFERRED
        pthread_cleanup_push(thread_test_cleanup_free, pnum);
        pthread_cleanup_push(thread_test_cleanup, pnum);
        while (1)
        {
                sleep(1);
                printf("thread test func sleep\n");
        }

        printf("thread test func free\n");
        free(pnum);

        pthread_cleanup_pop(0);
        pthread_cleanup_pop(0);
        return NULL;
}

int main (int argc, char *argv[])
{
        pthread_t threadid;

        if (pthread_create(&threadid, NULL, (void *)thread_test_func, NULL) != 0)
        {
                printf("thread create error: %s\n", strerror(errno));
                exit(-1);
        }

        sleep(3);

        printf("thread main(cancel): start\n");
        pthread_cancel(threadid);
        printf("thread main(cancel): finish\n");

        printf("thread main(join): start\n");
	    pthread_join(threadid, NULL);
        printf("thread main(join): finish\n");
        return 0;
}

/* 输出结果如下:
 * thread test func sleep
 * thread test func sleep
 * thread main(cancel): start
 * thread main(cancel): finish
 * thread main(join): start
 * thread test cleanup, num[0]=10
 * thread test cleanup, num[1]=20
 * thread test cleanup, num[2]=30
 * thread test cleanup, return
 * thread test cleanup free pnum
 * thread main(join): finish 
 */

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值