C pthreads + valgrind = утечка памяти: почему?

101
14

Я начинаю с pthreads в C, и я тоже маньяк, который, как я могу, могу написать мой код как "без ошибок".


Несмотря на попытку быть более осторожным, valgrind говорит мне, что я утечка памяти, независимо от погоды:


    Я создаю связанные потоки, которые я присоединяюсь к завершению (фрагмент кода 1)
    Я создаю связанные потоки, которые я отсоединяю после создания (фрагмент кода 2)
    Я создаю отдельные потоки (фрагмент кода 3)

Я знаю, что это уже обсуждалось (см. this, this, а также this), но мне все еще интересно:


    Почему на некоторых запусках я не получаю ошибок?
    Почему существует случайное количество общих mallocs() при работе с отдельными потоками? & Л; < ответ, предоставленный nos, фрагмент кода "фиксированный" с добавленной задержкой в ​​main()
    Почему "утечка памяти" сохраняется даже при работе с отдельными потоками? & Л; < то же, что и 2.

Как я понимаю из предыдущих ответов и следа valgrind, pthread_create() является основной причиной, расширяя стек, используемую потоками, по мере необходимости и повторно используя его время от времени, таким образом, несколько отсутствующих frees. Но что менее понятно, почему это зависит от исполнения и почему это происходит при создании отдельных потоков. Как я видел из определенных ответов, комментариев, а также от человека, ресурсы из выделенного потока будут освобождены после завершения потока. Я пробовал различные настройки, чтобы обойти это (добавлено время сна до конца каждого потока, до конца основного потока, увеличилось размер стека, добавлено больше "работы"...), но это не изменило конечный результат много. Кроме того, почему существует случайное число общих "mallocs()" при работе с отдельными потоками, не потеряет ли valgrind некоторые из отдельных потоков? Это также не зависит от размера стека.


Предоставленный код представляет собой примерный пример модели manager/workers, для которой подход joinable/join() для управления потоками представляется более подходящим imho.


Спасибо за любое просвещение, которое вы могли бы предоставить! Я также надеюсь, что эти (чрезмерно прокомментированные) фрагменты кода будут полезны всем, кто хочет начать работу с pthreads.


- swappy


PS Sys info: gcc на 64-битной арке debian


Фрагмент кода 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;
}


Фрагмент кода 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;
}


Фрагмент кода 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;
}


Выход Valgrind для фрагмента кода 1 (объединенные потоки и утечка памяти)


==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)

Выход Valgrind для фрагмента кода 1 (нет mem-leak, несколько прогонов позже)


--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)

спросил(а) 2013-03-04T20:50:00+04:00 7 лет, 3 месяца назад
1
Решение
90

У вас есть ошибка, когда ваши потоки отсоединены, вызывая поведение undefined.


В основном у вас есть эта строка кода:


struct args_for_job_t args[MAX_THREADS];

Что вы указали на указатели на рабочие потоки.

Затем main() достигает этой части


pthread_exit(NULL);

И main() перестает существовать, но у вас все еще могут быть рабочие потоки, которые обращаются к указанному выше массиву args, который находится в стеке main() - который больше не существует.
Ваши рабочие потоки могут завершиться до того, как main() закончится в некоторых прогонах, но не в других прогонах.

ответил(а) 2013-03-04T21:02:00+04:00 7 лет, 3 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема