多线程TaskQueue字符串内存处理不当引发内存泄漏问题排查
多线程TaskQueue内存泄漏问题排查
我给多线程程序实现了自定义TaskQueue,但代码存在内存分配错误。Valgrind检测到5块共440字节的确定内存泄漏,定位到do_task函数的while((endpoint = (char *)queueDequeue(queue)) != NULL)行。原本以为问题和strdup有关,但Valgrind没标注strdup为内存分配器,而且任务完成后已经释放了endpoint,还是有泄漏。
main.c代码
#include <pthread.h> #include <task_queue.h> #include <stdlib.h> #include <curl.h> typedef struct Threadargs { TaskQueue *q; CURLM *multi_handle; pthread_mutex_t lock; } Threadargs; void *do_task(void *args); int main(){ int len = 5; char *arr[] = { "filename.txt", "endpoint", "endpoint", "endpoint", "endpoint" }; TaskQueue *q = queueFromArr(arr, 1, len); curl_global_init(CURL_GLOBAL_ALL); CURLM *multi_handle = curl_multi_init(); curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); Threadargs *targs = malloc(sizeof(Threadargs)); targs->q = q; targs->multi_handle = multi_handle; pthread_mutex_init(&targs->lock, NULL); pthread_t threads[len]; for(int i = 0; i < len; ++i){ pthread_create(&threads[i], NULL, do_task, q); } int still_running = 0; do{ pthread_mutex_lock(&handle->lock); int pres = curl_multi_perform(handle->multi_handle, &still_running); pthread_mutex_unlock(&handle->lock); if(pres != CURLM_OK){ //print error here break; } int wres = curl_multi_poll(handle->multi_handle, NULL, 0, 1000, &numfds); if(wres != CURLM_OK){ //print error here break; } if (numfds == 0) { usleep(100000); } } while (still_running); pthread_mutex_lock(&targs->lock); while((msg = curl_multi_info_read(multi_handle, &msgs_left)) != NULL){ pthread_mutex_unlock(&targs->lock); CURL *easy_handle = msg->easy_handle; CURLcode return_code = msg->data.result; // for log file if(validateMulti(handle) != CURLM_OK) return CURLM_BAD_HANDLE; if(easy_handle == NULL) return CURLM_BAD_EASY_HANDLE; CURLMcode res; pthread_mutex_lock(&handle->lock); res = curl_multi_remove_handle(handle->multi_handle, easy_handle); // for debug pthread_mutex_unlock(&handle->lock); char *url; if(curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &url) != CURLE_OK) { //print error here continue; } free(url); curl_easy_cleanup(easy_handle); pthread_mutex_lock(&targs->lock); } pthread_mutex_unlock(&targs->lock); for(int i = 0; i < len; ++i){ pthread_join(threads[i], NULL); } free(threads); pthread_mutex_destroy(&targs->lock); return 0; } void *do_task(void *args){ Threadargs *targs = (Threadargs*)args; TaskQueue *q = targs->q; CURLM *multi = targs->multi_handle; char *endpoint; while((endpoint = (char *)queueDequeue(queue)) != NULL){ //error is here CURL *handle = curl_easy_init(); // set options for curl curl_easy_setopt(handle, CURLOPT_URL, endpoint); pthead_mutex_lock(&targs->lock); CURLcode res = curl_multi_add_handle(multi, handle); pthread_mutex_unlock(&targs->lock); if(res != CURLM_OK){ // print out error here return (void *) 1; } } return (void *) 0; }
task_queue.c代码
//#include <stdlib.h> //#include <stdio.h> //#include <string.h> typedef struct Task{ char *endpoint; struct Task *next; } Task; typedef struct TaskQueue{ Task *head; Task *tail; pthread_mutex_t lock; } TaskQueue; TaskQueue *queueInit() { TaskQueue *tqueue = malloc(sizeof(TaskQueue)); if(!tqueue) return NULL; tqueue->head = NULL; tqueue->tail = NULL; pthread_mutex_init(&tqueue->lock,NULL); return tqueue; } void queueEnqueue(TaskQueue *queue, char *endpoint) { Task *task = malloc(sizeof(Task)); task->endpoint = endpoint; task->next = NULL; pthread_mutex_lock(&queue->lock); if (queue->tail == NULL) { queue->head = queue->tail = task; } else { queue->tail->next = task; queue->tail = task; } pthread_mutex_unlock(&queue->lock); } TaskQueue *queueFromArr(char **arr, int start, int end) { TaskQueue *queue; if((queue = queueInit()) == NULL) return NULL; for(int i = start; i < end; ++i){ char *point = strdup(arr[i]); queueEnqueue(queue,point); } return queue; } // Dequeue a task from the queue void *queueDequeue(TaskQueue *queue) { pthread_mutex_lock(&queue->lock); if (queue->head == NULL) { pthread_mutex_unlock(&queue->lock); return NULL; } Task *temp = queue->head; char *data = strdup(temp->endpoint); queue->head = temp->next; if (queue->head == NULL) queue->tail = NULL; free(temp); pthread_mutex_unlock(&queue->lock); return data; }
Valgrind检测结果
==62212== 440 bytes in 5 blocks are definitely lost in loss record 147 of 510 ==62212== at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==62212== by 0x1204675E: make_call (main.c:316) ==62212== by 0x4A36AC2: start_thread (pthread_create.c:442) ==62212== by 0x4AC7A03: clone (clone.S:100)
问题根源分析
- 重复拷贝导致内存泄漏:
queueFromArr中用strdup创建了endpoint的拷贝并存入队列,而queueDequeue里又做了一次strdup返回给调用者。每个endpoint被拷贝两次,但你只释放了一次(curl处理后的free(url)),队列初始化时的第一次strdup内存完全没被释放。 - 线程参数传递错误:主线程创建线程时传的是
q,但do_task里把参数强转为Threadargs*,会导致未定义行为,引发内存访问混乱。 - 栈变量错误释放:
threads是栈上的数组,调用free(threads)属于非法操作,会破坏栈结构。 - 代码笔误:
do_task里的pthead_mutex_lock拼写错误;主线程循环中使用了未定义的handle变量,应为targs。
修复方案
- 移除不必要的strdup:修改
queueDequeue直接返回原指针,避免重复拷贝:
void *queueDequeue(TaskQueue *queue) { pthread_mutex_lock(&queue->lock); if (queue->head == NULL) { pthread_mutex_unlock(&queue->lock); return NULL; } Task *temp = queue->head; char *data = temp->endpoint; // 直接取原指针,不再重复strdup queue->head = temp->next; if (queue->head == NULL) queue->tail = NULL; free(temp); // 仅释放Task结构体,endpoint内存留到后续处理 pthread_mutex_unlock(&queue->lock); return data; }
- 修正线程参数传递:主线程创建线程时传入
targs:
pthread_create(&threads[i], NULL, do_task, targs);
- 清理TaskQueue资源:主线程末尾添加队列销毁逻辑:
// 在pthread_join之后添加 pthread_mutex_destroy(&q->lock); free(q);
- 修正代码笔误:将
pthead_mutex_lock改为pthread_mutex_lock;主线程循环中的handle全部替换为targs;删除free(threads)语句。
内容的提问来源于stack exchange,提问作者BaccaRuler MC
相关产品推荐
相关产品推荐

