You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程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)

问题根源分析

  1. 重复拷贝导致内存泄漏:queueFromArr中用strdup创建了endpoint的拷贝并存入队列,而queueDequeue里又做了一次strdup返回给调用者。每个endpoint被拷贝两次,但你只释放了一次(curl处理后的free(url)),队列初始化时的第一次strdup内存完全没被释放。
  2. 线程参数传递错误:主线程创建线程时传的是q,但do_task里把参数强转为Threadargs*,会导致未定义行为,引发内存访问混乱。
  3. 栈变量错误释放:threads是栈上的数组,调用free(threads)属于非法操作,会破坏栈结构。
  4. 代码笔误:do_task里的pthead_mutex_lock拼写错误;主线程循环中使用了未定义的handle变量,应为targs。

修复方案

  1. 移除不必要的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; 
}
  1. 修正线程参数传递:主线程创建线程时传入targs:
pthread_create(&threads[i], NULL, do_task, targs);
  1. 清理TaskQueue资源:主线程末尾添加队列销毁逻辑:
// 在pthread_join之后添加
pthread_mutex_destroy(&q->lock);
free(q);
  1. 修正代码笔误:将pthead_mutex_lock改为pthread_mutex_lock;主线程循环中的handle全部替换为targs;删除free(threads)语句。

内容的提问来源于stack exchange,提问作者BaccaRuler MC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 01:02:36