6

私は C で pthreads を使い始めており、可能な限り「バグのない」コードを書くマニアでもあります。

細心の注意を払っているにもかかわらず、valgrind は、天候に関係なく、メモリ リークが発生していると言っています。

  1. 完了時に参加する参加可能なスレッドを作成します (コード スニペット 1)
  2. 作成後にデタッチする結合可能なスレッドを作成します (コード スニペット 2)
  3. 切り離されたスレッドを作成します (コード スニペット 3)

これについてはすでに議論されていることは知っていますが(これこれ、およびこれも参照)、次の点についてはまだ興味があります。

  1. 特定の実行でエラーが発生しないのはなぜですか?
  2. デタッチされたスレッドを処理するときに、全体的な mallocs() の数が乱数であるように見えるのはなぜですか? << 回答は nos によって提供され、コード スニペットは「修正」され、main() に遅延が追加されています。
  3. デタッチされたスレッドを処理しても「メモリリーク」が続くのはなぜですか? << 2と同じ。

以前の回答と valgrind トレースからわかるように、pthread_create() が根本的な原因であり、必要に応じてスレッドが使用するスタックを拡張し、時々再利用するため、いくつかの解放が失われます。しかし、あまり明確でないのは、実行の実行に依存する理由と、デタッチされたスレッドを作成するときにも発生する理由です。特定の回答、コメント、および男性から見たように、切り離されたスレッドからのリソースは、スレッドの完了時に解放されます。これを回避するためにさまざまな調整を試みました (各スレッドの終了前にスリープ時間を追加し、メインスレッドの終了前にスリープ時間を追加し、スタックサイズを増やし、「作業」を追加しました...)、変更しませんでしたはるかに最終結果。また、デタッチされたスレッドを処理するときに、全体的な「mallocs()」の数が乱数になるのはなぜですか? valgrind は切り離されたスレッドの一部を追跡できませんか? これもスタックサイズには依存しないようです。

提供されているコードは、スレッド管理への joinable/join() アプローチがより適していると思われるマネージャー/ワーカー モデルのモック例です。

あなたが提供できるかもしれない啓発をありがとう!また、これらの (過度にコメントされた) コードのスニペットが、pthread を使い始めたいと考えているすべての人に役立つことを願っています。

- スワッピー

PS システム情報: debian 64bit arch の gcc

コード スニペット 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;
}

コード スニペット 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)

コード スニペット 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)
4

1 に答える 1

5

スレッドが切り離されたときにバグが発生し、未定義の動作が発生します。

主に次のコード行があります。

struct args_for_job_t args[MAX_THREADS];

ワーカースレッドへのポインターを渡します。

次に、main()がこの部分に到達します

pthread_exit(NULL);

そして、main()は存在しなくなりますがargs、main()のスタック上にある上記の配列にアクセスするワーカースレッドがまだ存在している可能性があります。これはもう存在しません。一部の実行ではmain()が終了する前にワーカースレッドがすべて終了する場合がありますが、他の実行では終了しない場合があります。

于 2013-03-04T18:02:42.237 に答える