问题描述:
程序开始时正常创建(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
*/