2013-03-04 28 views
6

Zaczynam od pthreadów w C i jestem również maniakiem pisania mojego kodu jako "wolnego od błędów", jak tylko mogę.c pthreads + valgrind = wyciek pamięci: dlaczego?

Pomimo stara się być bardzo ostrożny, valgrind mówi mi, że mam wyciek pamięci, niezależnie pogody:

  1. tworzę PŁYCIE wątki, które łączę się po zakończeniu (kod urywek 1)
  2. tworzę PŁYCIE nici że odrywają po stworzeniu (fragment kodu 2)
  3. tworzę wolnostojący wątki (fragment kodu 3)

wiem, że to już zostało omówione (patrz this, this a także this), ale wciąż jestem ciekaw:

  1. Dlaczego na niektórych tras skończę bez błędów?
  2. Dlaczego wydaje się, że istnieje losowa liczba ogólnych mallocs() w przypadku oddzielnych wątków? < < odpowiedź dostarczona przez nos, fragment kodu "naprawiony" z dodatkowym opóźnieniem w głównej()
  3. Dlaczego "wyciek pamięci" utrzymuje się nawet w przypadku odłączonych wątków? < < same jak 2.

Jak zrozumieć z poprzednich odpowiedzi i śladu valgrind, pthread_create() jest główną przyczyną, rozszerzając stos używany przez nici i w razie potrzeby ponowne jej w czasie, tak więc kilka brakujących uwalnia. Ale mniej jasne jest, dlaczego zależy to od uruchomienia wykonania i dlaczego tak się dzieje również podczas tworzenia odłączonych wątków. Jak widziałem z niektórych odpowiedzi, komentarzy, a także od człowieka, zasoby z oddzielonego wątku zostaną uwolnione po zakończeniu wątku. Próbowałem różnych usprawnień, aby obejść to (dodałem czas snu przed końcem każdego wątku, przed końcem głównego wątku, zwiększono rozmiar stosu, dodałem więcej "pracy" ...), ale to nie zmieniło końcowy wynik o wiele. Ponadto, dlaczego istnieje losowa liczba ogólnych "mallocs()", gdy mamy do czynienia z odłączonymi wątkami, czy valgrind traci część z oddzielonych wątków? Wydaje się to również nie zależeć od wielkości stosu.

Podany kod jest fałszywym przykładem modelu menedżera/pracownika, dla którego bardziej odpowiednie wydaje się podejście joinable/join() do zarządzania wątkami.

Dziękuję za każde oświecenie, które możesz zapewnić! Mam też nadzieję, że te (skomentowane) fragmenty kodu będą pomocne każdemu, kto chce zacząć z pthreads.

- swappy

informacji

PS Sys: GCC na debian 64-bitowego łuku

Fragment kodu 1 (montażu zestawowego nici połączone)

/* 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; 
} 

Fragment kodu 2 (oderwane wątki po utworzeniu):

/* 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; 
} 

Fragment kodu 3 (oderwane nici po utworzeniu):

/* 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; 
} 

wyjście Valgrind do fragmentu kodu 1 (połączonych gwintem & MEM przecieku)

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

wyjściu valgrind do fragmentu kodu 1 (brak wycieku mem, kilka przebiegów później)

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

Jaki jest wynik valgrind? (Na marginesie: Nie ma potrzeby używania 'pthread_exit' w dowolnym miejscu tego kodu, możesz zamiast tego po prostu' return 0; '). –

+0

Zastosował twoją korektę z powrotem 0; zamiast pthread_exit() w main(). Dodałem także dane wyjściowe valgrind dla fragmentu kodu join(), zwiększając czas zakończenia main(), zgodnie z sugestią nosa, który wyjaśnił moje drugie i trzecie pytanie. – swappy

+0

To naprawdę nie jest wyciek pamięci, cała pamięć jest wciąż dostępna –

Odpowiedz

5

Masz błąd, gdy wątki są odłączone, powodując niezdefiniowane zachowanie.

W głównym masz ten wiersz kodu:

struct args_for_job_t args[MAX_THREADS]; 

Którą ręką wskaźników do wątków roboczych.

Następnie main() osiągnie tę część

pthread_exit(NULL); 

A main() przestaje istnieć, ale nadal mogą mieć wątków roboczych dookoła, że ​​dostęp do wyżej args tablicę, która znajduje się na stosie main() - co już nie istnieje. Twoje wątki robocze mogą być zakończone przed zakończeniem main() w niektórych seriach, ale nie w innych przebiegach.

+2

Dzięki nos, podejrzewałam takie zachowanie, ale chciałem dokładnie sprawdzić. Chodzi o to, próbowałem również dodać timer (usleep()) przed pthread_exit (NULL) (lub powrotu 0, jak zasugerował Jonathan) i nadal zachowywał się losowo. Żyłem pod wrażeniem, że użycie pthread_exit() zamiast powrotu powie, że wątek main() zawiesi się, dopóki wszyscy robotnicy nie zostaną skończeni (dokładnie to, co robi pthread_join()). – swappy

+0

Poprawiam się, zderzyłem czas snu od 10 000 do 100 000 z odłączonymi wątkami i wygląda na to, że wszystkie wycieki pamięci zniknęły, pozostawia to wystarczająco dużo czasu na ukończenie wszystkich wątków przed zakończeniem funkcji main(). – swappy

+0

Przepraszam, masz rację, że 'pthread_exit' w' main' powoduje, że czeka. Wywołania do 'pthread_exit' w innych wątkach i we fragmencie 1 są zbędne –