2013-03-04 48 views
6

Tôi đang bắt đầu với pthreads trong C và tôi cũng là một maniac viết mã của tôi là "không có lỗi" như tôi có thể có thể.c pthreads + valgrind = rò rỉ bộ nhớ: tại sao?

Mặc dù cố gắng hết sức cẩn thận, valgrind đang nói với tôi rằng tôi đang bị rò rỉ bộ nhớ, bất kể thời tiết:

  1. tôi có thể tạo chủ đề joinable mà tôi tham gia sau khi hoàn thành (đoạn mã 1)
  2. tôi tạo joinable chủ đề mà tôi tách sau khi tạo (đoạn mã 2)
  3. tôi có thể tạo chủ đề tách ra (mã đoạn 3)

tôi biết điều này đã được thảo luận (xem this, this và cũng this), nhưng tôi vẫn tò mò như:

  1. Tại sao trên một số chạy tôi kết thúc với không có lỗi?
  2. Tại sao có vẻ là một số ngẫu nhiên của tổng thể mallocs() khi giao dịch với các chuỗi được tách ra? < < câu trả lời được cung cấp bởi nos, đoạn mã "cố định" với độ trễ được thêm vào chính()
  3. Tại sao "rò rỉ bộ nhớ" vẫn tồn tại ngay cả khi xử lý các luồng tách ra? < < giống như 2.

Theo tôi được biết từ các câu trả lời trước và dấu vết valgrind, pthread_create() là nguyên nhân gốc rễ, mở rộng ngăn xếp được sử dụng bởi chủ đề khi cần thiết và tái sử dụng nó vào những thời điểm, do đó một vài thiếu giải phóng. Nhưng điều ít rõ ràng hơn là tại sao nó phụ thuộc vào chạy thực thi và tại sao nó cũng xảy ra khi tạo các luồng tách ra. Như tôi đã thấy từ một số câu trả lời, ý kiến ​​và cũng từ người đàn ông, các tài nguyên từ một sợi tách rời sẽ được giải phóng khi hoàn thành chuỗi. Tôi đã thử các chỉnh sửa khác nhau để làm việc xung quanh điều này (thêm một thời gian ngủ trước khi kết thúc mỗi chủ đề, trước khi kết thúc của chủ đề chính, tăng kích thước ngăn xếp, thêm nhiều hơn "công việc" ...) nhưng nó không thay đổi kết quả cuối cùng bởi nhiều. Ngoài ra, tại sao có một số ngẫu nhiên của tổng thể "mallocs()" khi giao dịch với các chủ đề tách ra, không valgrind mất theo dõi của một số chủ đề tách ra? Điều này cũng không có vẻ phụ thuộc vào kích thước ngăn xếp.

Mã được cung cấp là một ví dụ giả định về mô hình quản lý/công nhân mà cách tiếp cận tham gia/tham gia() để quản lý chuỗi có vẻ phù hợp hơn.

Cảm ơn bất kỳ sự khai sáng nào bạn có thể cung cấp! Tôi cũng hy vọng những đoạn mã (over-comment) này sẽ hữu ích cho bất kỳ ai muốn bắt đầu với pthreads.

- swappy

PS Sys thông tin: gcc trên debian 64bit vòm

Mã đoạn mã 1 (đề joinable tham gia):

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

Mã đoạn mã 2 (đề tách ra sau khi tạo):

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

Mã đoạn mã 3 (đề tách ra khi tạo):

/* 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 đầu ra cho đoạn mã 1 (đề tham gia & mem-rò rỉ)

==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 đầu ra cho đoạn mã 1 (không có mem-leak, một vài lần chạy sau)

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

Sản lượng valgrind là gì? (Ngoài ra: Không cần sử dụng 'pthread_exit' ở bất kỳ đâu trong mã đó, bạn chỉ có thể' trả về 0; 'thay thế.) –

+0

Áp dụng hiệu chỉnh của bạn với trả về 0; thay vì pthread_exit() trong main(). Ngoài ra, tôi đã thêm đầu ra valgrind cho đoạn mã nối(), tăng thời gian hoàn thành cho hàm main() như được đề xuất bởi nos xóa các câu hỏi thứ hai và thứ ba của tôi. – swappy

+0

Đó không thực sự là một rò rỉ bộ nhớ, tất cả bộ nhớ vẫn có thể truy cập –

Trả lời

5

Bạn có một lỗi trong khi chủ đề của bạn được tách ra, gây ra hành vi không xác định.

Trong chính bạn có dòng mã này:

struct args_for_job_t args[MAX_THREADS]; 

Mà bạn tay của con trỏ để đề người lao động của mình.

Sau đó main() đạt đến phần này

pthread_exit(NULL); 

Và main() không còn tồn tại, nhưng bạn vẫn có thể có đề người lao động xung quanh, mà truy cập vào args mảng ở trên đó là trên stack của main() - không còn tồn tại nữa. Chủ đề công nhân của bạn có thể kết thúc trước khi main() kết thúc trong một số lần chạy, nhưng không phải trong các lần chạy khác.

+2

Cảm ơn bạn, tôi đã nghi ngờ hành vi như vậy nhưng muốn kiểm tra lại. Thing là, tôi cũng đã thử thêm một bộ đếm thời gian (usleep()) trước khi pthread_exit (NULL) (hoặc trả về 0 như Jonathan đề xuất) và nó vẫn cư xử ngẫu nhiên. Tôi đã sống dưới ấn tượng rằng bằng cách sử dụng pthread_exit() thay vì trả về sẽ cho biết thread chính() treo cho đến khi tất cả các công nhân được thực hiện (chính xác những gì pthread_join() nào). – swappy

+0

Tôi chỉnh sửa, tôi đã tăng thời gian ngủ từ 10 000 lên 100 000 với các luồng tách ra và dường như tất cả các rò rỉ bộ nhớ đều biến mất, điều này để lại đủ thời gian cho tất cả các luồng hoàn thành trước khi hàm main() kết thúc. – swappy

+0

Xin lỗi, bạn nói đúng rằng 'pthread_exit' trong' main' khiến nó phải đợi. Các cuộc gọi đến 'pthread_exit' trong các chủ đề khác và trong đoạn 1 là dư thừa –

Các vấn đề liên quan