【问题标题】:c pthreads + valgrind = memory leak : why?c pthreads + valgrind = 内存泄漏:为什么?
【发布时间】:2013-03-04 17:50:09
【问题描述】:

我开始使用 C 语言中的 pthreads,而且我还热衷于将我的代码编写为尽可能“无错误”。

尽管尝试格外小心,但 valgrind 告诉我,无论天气如何,我都在泄漏内存:

  1. 我创建了在完成时加入的可连接线程(代码 sn-p 1)
  2. 我创建了可连接的线程,创建后我将其分离(代码 sn-p 2)
  3. 我创建了分离的线程(代码 sn-p 3)

我知道这已经讨论过了(见thisthisthis),但我仍然很好奇:

  1. 为什么在某些运行中我最终没有错误?
  2. 为什么在处理分离线程时总的 malloc() 似乎是随机数?
  3. 为什么即使在处理分离的线程时“内存泄漏”仍然存在?

正如我从之前的答案和 valgrind 跟踪中了解到的那样,pthread_create() 是根本原因,它根据需要扩展线程使用的堆栈并有时重用它,因此缺少一些释放。但不太清楚的是为什么它取决于执行运行以及为什么在创建分离线程时也会发生这种情况。正如我从某些答案、cmets 和 man 中看到的那样,来自分离线程的资源将在线程完成后释放。我已经尝试了各种调整来解决这个问题(在每个线程结束之前,在主线程结束之前添加一个睡眠时间,增加堆栈大小,添加更多“工作”......)但它并没有改变最终结果很多。此外,为什么在处理分离线程时会有随机数量的整体“mallocs()”,valgrind 是否会丢失一些分离线程的跟踪?这似乎也不取决于堆栈大小。

提供的代码是一个经理/工人模型的模拟示例,对于该模型,线程管理的 joinable/join() 方法似乎更适合恕我直言。

感谢您提供的任何启发!我也希望这些(过度注释的)sn-ps 代码对希望开始使用 pthread 的人有所帮助。

- 交换的

PS 系统信息:debian 64 位架构上的 gcc

代码 sn-p 1(加入的可连接线程):

/* Running this multiple times with valgrind, I sometimes end with :
    - no errors (proper malloc/free balance) 
    - 4 extra malloc vs free (most frequently) 
   The number of mallocs() is more conservative and depends on the number of threads. 
*/

#include <stdlib.h>             /* EXIT_FAILURE, EXIT_SUCCESS macros & the likes */
#include <stdio.h>              /* printf() & the likes */
#include <pthread.h>            /* test subject */

#define MAX_THREADS 100         /* Number of threads */
pthread_attr_t tattr;           /* Thread attribute */
pthread_t workers[MAX_THREADS]; /* All the threads spawned by the main() thread */

/* A mock container structure to pass arguments around */
struct args_for_job_t {
    int tid;
    int status;
};

/* The job each worker will perform upon creation */
void *job(void *arg)
{
    /* Cast arguments in a proper container */
    struct args_for_job_t *container;
    container = (struct args_for_job_t *)arg;

    /* A mock job */
    printf("[TID - %d]\n", container->tid);

    /* Properly exit with status code tid */
    pthread_exit((void *)(&container->status));
}

int main ()
{
    int return_code;                            /* Will hold return codes */
    void *return_status;                        /* Will hold return status */
    int tid;                                    /* Thread id */
    struct args_for_job_t args[MAX_THREADS];    /* For thread safeness */

    /* Initialize and set thread joinable attribute */
    pthread_attr_init(&tattr);
    pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_JOINABLE);

    /* Spawn detached threads */
    for (tid = 0; tid < MAX_THREADS; tid++)
    {
        args[tid].tid = tid;
        args[tid].status = tid;
        return_code = pthread_create(&workers[tid], &tattr, job, (void *)(&args[tid]));
        if (return_code != 0) { printf("[ERROR] Thread creation failed\n"); return EXIT_FAILURE; }
    }

    /* Free thread attribute */
    pthread_attr_destroy(&tattr);

    /* Properly join() all workers before completion */
    for(tid = 0; tid < MAX_THREADS; tid++)
    {
        return_code = pthread_join(workers[tid], &return_status);
        if (return_code != 0)
        {
            printf("[ERROR] Return code from pthread_join() is %d\n", return_code);
            return EXIT_FAILURE;
        }
        printf("Thread %d joined with return status %d\n", tid, *(int *)return_status);
    }

    return EXIT_SUCCESS;
}

代码 sn-p 2(创建后分离线程):

/* Running this multiple times with valgrind, I sometimes end with :
    - no errors (proper malloc/free balance) 
    - 1 extra malloc vs free (most frequently) 
   Most surprisingly, it seems there is a random amount of overall mallocs 
*/

#include <stdlib.h>             /* EXIT_FAILURE, EXIT_SUCCESS macros & the likes */
#include <stdio.h>              /* printf() & the likes */
#include <pthread.h>            /* test subject */
#include <unistd.h>         

#define MAX_THREADS 100         /* Number of threads */
pthread_attr_t tattr;           /* Thread attribute */
pthread_t workers[MAX_THREADS]; /* All the threads spawned by the main() thread */

/* A mock container structure to pass arguments around */
struct args_for_job_t {
    int tid;
};

/* The job each worker will perform upon creation */
void *job(void *arg)
{
    /* Cast arguments in a proper container */
    struct args_for_job_t *container;
    container = (struct args_for_job_t *)arg;

    /* A mock job */
    printf("[TID - %d]\n", container->tid);

    /* For the sake of returning something, not necessary */
    return NULL;
}

int main ()
{
    int return_code;                            /* Will hold return codes */
    int tid;                                    /* Thread id */
    struct args_for_job_t args[MAX_THREADS];    /* For thread safeness */

    /* Initialize and set thread joinable attribute */
    pthread_attr_init(&tattr);
    pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_JOINABLE);

    /* Spawn detached threads */
    for (tid = 0; tid < MAX_THREADS; tid++)
    {
        args[tid].tid = tid;
        return_code = pthread_create(&workers[tid], &tattr, job, (void *)(&args[tid]));
        if (return_code != 0) { printf("[ERROR] Thread creation failed\n"); return EXIT_FAILURE; }
        /* Detach worker after creation */
        pthread_detach(workers[tid]);
    }

    /* Free thread attribute */
    pthread_attr_destroy(&tattr);

    /* Delay main() completion until all detached threads finish their jobs. */
    usleep(100000);
    return EXIT_SUCCESS;
}

代码 sn-p 3(创建时分离线程):

/* Running this multiple times with valgrind, I sometimes end with :
    - no errors (proper malloc/free balance) 
    - 1 extra malloc vs free (most frequently) 
   Most surprisingly, it seems there is a random amount of overall mallocs 
*/

#include <stdlib.h>             /* EXIT_FAILURE, EXIT_SUCCESS macros & the likes */
#include <stdio.h>              /* printf() & the likes */
#include <pthread.h>            /* test subject */

#define MAX_THREADS 100         /* Number of threads */
pthread_attr_t tattr;           /* Thread attribute */
pthread_t workers[MAX_THREADS]; /* All the threads spawned by the main() thread */

/* A mock container structure to pass arguments around */
struct args_for_job_t {
    int tid;
};

/* The job each worker will perform upon creation */
void *job(void *arg)
{
    /* Cast arguments in a proper container */
    struct args_for_job_t *container;
    container = (struct args_for_job_t *)arg;

    /* A mock job */
    printf("[TID - %d]\n", container->tid);

    /* For the sake of returning something, not necessary */
    return NULL;
}

int main ()
{
    int return_code;                            /* Will hold return codes */
    int tid;                                    /* Thread id */
    struct args_for_job_t args[MAX_THREADS];    /* For thread safeness */

    /* Initialize and set thread detached attribute */
    pthread_attr_init(&tattr);
    pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);

    /* Spawn detached threads */
    for (tid = 0; tid < MAX_THREADS; tid++)
    {
        args[tid].tid = tid;
        return_code = pthread_create(&workers[tid], &tattr, job, (void *)(&args[tid]));
        if (return_code != 0) { printf("[ERROR] Thread creation failed\n"); return EXIT_FAILURE; }
    }

    /* Free thread attribute */
    pthread_attr_destroy(&tattr);

    /* Delay main() completion until all detached threads finish their jobs. */
    usleep(100000);
    return EXIT_SUCCESS;
}

代码 sn-p 1 的 Valgrind 输出(加入线程和内存泄漏)

==27802== 
==27802== HEAP SUMMARY:
==27802==     in use at exit: 1,558 bytes in 4 blocks
==27802==   total heap usage: 105 allocs, 101 frees, 28,814 bytes allocated
==27802== 
==27802== Searching for pointers to 4 not-freed blocks
==27802== Checked 104,360 bytes
==27802== 
==27802== 36 bytes in 1 blocks are still reachable in loss record 1 of 4
==27802==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==27802==    by 0x400894D: _dl_map_object (dl-load.c:162)
==27802==    by 0x401384A: dl_open_worker (dl-open.c:225)
==27802==    by 0x400F175: _dl_catch_error (dl-error.c:178)
==27802==    by 0x4013319: _dl_open (dl-open.c:639)
==27802==    by 0x517F601: do_dlopen (dl-libc.c:89)
==27802==    by 0x400F175: _dl_catch_error (dl-error.c:178)
==27802==    by 0x517F6C3: __libc_dlopen_mode (dl-libc.c:48)
==27802==    by 0x4E423BB: pthread_cancel_init (unwind-forcedunwind.c:53)
==27802==    by 0x4E4257B: _Unwind_ForcedUnwind (unwind-forcedunwind.c:130)
==27802==    by 0x4E4069F: __pthread_unwind (unwind.c:130)
==27802==    by 0x4E3AFF4: pthread_exit (pthreadP.h:265)
==27802== 
==27802== 36 bytes in 1 blocks are still reachable in loss record 2 of 4
==27802==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==27802==    by 0x400B7EC: _dl_new_object (dl-object.c:161)
==27802==    by 0x4006805: _dl_map_object_from_fd (dl-load.c:1051)
==27802==    by 0x4008699: _dl_map_object (dl-load.c:2568)
==27802==    by 0x401384A: dl_open_worker (dl-open.c:225)
==27802==    by 0x400F175: _dl_catch_error (dl-error.c:178)
==27802==    by 0x4013319: _dl_open (dl-open.c:639)
==27802==    by 0x517F601: do_dlopen (dl-libc.c:89)
==27802==    by 0x400F175: _dl_catch_error (dl-error.c:178)
==27802==    by 0x517F6C3: __libc_dlopen_mode (dl-libc.c:48)
==27802==    by 0x4E423BB: pthread_cancel_init (unwind-forcedunwind.c:53)
==27802==    by 0x4E4257B: _Unwind_ForcedUnwind (unwind-forcedunwind.c:130)
==27802== 
==27802== 312 bytes in 1 blocks are still reachable in loss record 3 of 4
==27802==    at 0x4C29DB4: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==27802==    by 0x4010B59: _dl_check_map_versions (dl-version.c:300)
==27802==    by 0x4013E1F: dl_open_worker (dl-open.c:268)
==27802==    by 0x400F175: _dl_catch_error (dl-error.c:178)
==27802==    by 0x4013319: _dl_open (dl-open.c:639)
==27802==    by 0x517F601: do_dlopen (dl-libc.c:89)
==27802==    by 0x400F175: _dl_catch_error (dl-error.c:178)
==27802==    by 0x517F6C3: __libc_dlopen_mode (dl-libc.c:48)
==27802==    by 0x4E423BB: pthread_cancel_init (unwind-forcedunwind.c:53)
==27802==    by 0x4E4257B: _Unwind_ForcedUnwind (unwind-forcedunwind.c:130)
==27802==    by 0x4E4069F: __pthread_unwind (unwind.c:130)
==27802==    by 0x4E3AFF4: pthread_exit (pthreadP.h:265)
==27802== 
==27802== 1,174 bytes in 1 blocks are still reachable in loss record 4 of 4
==27802==    at 0x4C29DB4: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==27802==    by 0x400B57D: _dl_new_object (dl-object.c:77)
==27802==    by 0x4006805: _dl_map_object_from_fd (dl-load.c:1051)
==27802==    by 0x4008699: _dl_map_object (dl-load.c:2568)
==27802==    by 0x401384A: dl_open_worker (dl-open.c:225)
==27802==    by 0x400F175: _dl_catch_error (dl-error.c:178)
==27802==    by 0x4013319: _dl_open (dl-open.c:639)
==27802==    by 0x517F601: do_dlopen (dl-libc.c:89)
==27802==    by 0x400F175: _dl_catch_error (dl-error.c:178)
==27802==    by 0x517F6C3: __libc_dlopen_mode (dl-libc.c:48)
==27802==    by 0x4E423BB: pthread_cancel_init (unwind-forcedunwind.c:53)
==27802==    by 0x4E4257B: _Unwind_ForcedUnwind (unwind-forcedunwind.c:130)
==27802== 
==27802== LEAK SUMMARY:
==27802==    definitely lost: 0 bytes in 0 blocks
==27802==    indirectly lost: 0 bytes in 0 blocks
==27802==      possibly lost: 0 bytes in 0 blocks
==27802==    still reachable: 1,558 bytes in 4 blocks
==27802==         suppressed: 0 bytes in 0 blocks
==27802== 
==27802== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
--27802-- 
--27802-- used_suppression:      2 dl-hack3-cond-1
==27802== 
==27802== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

代码 sn-p 1 的 Valgrind 输出(没有内存泄漏,稍后运行几次)

--29170-- Discarding syms at 0x64168d0-0x6426198 in /lib/x86_64-linux-gnu/libgcc_s.so.1 due to munmap()
==29170== 
==29170== HEAP SUMMARY:
==29170==     in use at exit: 0 bytes in 0 blocks
==29170==   total heap usage: 105 allocs, 105 frees, 28,814 bytes allocated
==29170== 
==29170== All heap blocks were freed -- no leaks are possible
==29170== 
==29170== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
--29170-- 
--29170-- used_suppression:      2 dl-hack3-cond-1
==29170== 
==29170== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

【问题讨论】:

  • valgrind 的输出是什么? (旁白:无需在该代码中的任何位置使用pthread_exit,您可以改为使用return 0;。)
  • 应用您的更正并返回 0;而不是 main() 中的 pthread_exit()。此外,我为joined() 代码sn-p 添加了valgrind 输出,增加了main() 的完成时间,正如nos 所建议的,清除了我的第二个和第三个问题。
  • 这不是真正的内存泄漏,所有内存仍然可以访问
  • 抱歉,我对pthread_exit 的评论仅与sn-p 1 相关,它没用(因为您加入了线程)。在 sn-ps 2 和 3 中,在main 的末尾调用它是正确的
  • 同意,这不是真正意义上的内存泄漏,它根本不是错误,但让我烦恼的是为什么在某些运行中所有内容都被正确释放,而在大多数情况下却没有.

标签: c memory-leaks pthreads valgrind


【解决方案1】:

线程分离时存在错误,导致未定义的行为。

在 main 你有这行代码:

struct args_for_job_t args[MAX_THREADS];

你手上的指针指向你的工作线程。

然后main()到达这部分

pthread_exit(NULL);

并且 main() 不再存在,但您仍然可能有工作线程,它访问上面的 args 数组,该数组位于 main() 的堆栈中 - 不再存在。 在某些运行中,您的工作线程可能在 main() 结束之前全部完成,但在其他运行中不会。

【讨论】:

  • 谢谢,不,我怀疑这种行为,但想仔细检查。问题是,我还尝试在 pthread_exit(NULL) 之前添加一个计时器 (usleep()) (或按照乔纳森的建议返回 0),它仍然随机运行。我的印象是使用 pthread_exit() 而不是 return 会告诉 main() 线程挂起,直到所有工作人员都完成(正是 pthread_join() 所做的)。
  • 我已经更正了,我使用分离的线程将睡眠时间从 10 000 增加到 100 000,并且似乎所有内存泄漏都消失了,这为所有线程留下了足够的时间在 main() 终止之前完成.
  • 抱歉,main 中的 pthread_exit 导致它等待是对的。在其他线程和 sn-p 1 中对 pthread_exit 的调用是多余的
  • 好吧,它似乎不会导致它在处理分离线程时在 main() 中等待,如果我增加等待时间以“强制”它等待没有错误。我将 pthread_exit() 留在分离线程中的原因是为了尽量减少从一个版本到另一个版本的代码更改,但我同意它是多余的,我将在 sn-ps 中解决这个问题。谢谢!
  • Ahmm 实际上我意识到我确实在发布的版本中删除了它们,我添加了“return NULL;”只是为了它,但它确实是多余的。我仍然希望将 pthread_exit() 保留在加入版本中,因为我将依赖返回状态来完成其他工作。
猜你喜欢
  • 2020-03-31
  • 2016-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-28
  • 1970-01-01
  • 2013-06-24
  • 2019-07-02
相关资源
最近更新 更多