web-dev-qa-db-ja.com

pthread_exitとreturn

以下のように定義された参加可能なpthreadランナー関数があります。

void *sumOfProducts(void *param)
{
...
pthread_exit(0);
}

このスレッドはメインスレッドに参加することになっています。

Valgrindを介してプログラムを実行すると、常に以下のリークが表示されます。

LEAK SUMMARY:
   definitely lost: 0 bytes in 0 blocks
   indirectly lost: 0 bytes in 0 blocks
     possibly lost: 0 bytes in 0 blocks
   still reachable: 968 bytes in 5 blocks
        suppressed: 0 bytes in 0 blocks

ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 10)

私は言ったpthreadsのmanページをチェックしました:

  The new thread terminates in one of the following ways:

   * It  calls  pthread_exit(3),  specifying  an exit status value that is
     available  to  another  thread  in  the  same  process   that   calls
     pthread_join(3).

   * It  returns  from  start_routine().   This  is  equivalent to calling
     pthread_exit(3) with the value supplied in the return statement.

   * It is canceled (see pthread_cancel(3)).

   * Any of the threads in the process calls exit(3), or the  main  thread
     performs  a  return  from main().  This causes the termination of all
     threads in the process.

奇妙なことに、pthread_exit()をreturnステートメントに置き換えたところ、リークが消えました

return(NULL);

私の実際の質問は3つの方向に分かれています:

  1. 誰かがreturnステートメントがリークを引き起こさなかった理由を説明できますか?
  2. スレッドからの脱出に関して、両方のステートメントの間に基本的な違いはありますか?
  3. もしそうなら、一方が他方よりいつ好まれるべきですか?
37
user191776

次の最小限のテストケースは、説明した動作を示しています。

_#include <pthread.h>
#include <unistd.h>

void *app1(void *x)
{
    sleep(1);
    pthread_exit(0);
}

int main()
{
    pthread_t t1;

    pthread_create(&t1, NULL, app1, NULL);
    pthread_join(t1, NULL);

    return 0;
}
_

_valgrind --leak-check=full --show-reachable=yes_は、解放されていないが、プロセスの終了時に到達可能なpthread_exit()によって呼び出された関数から割り当てられた5つのブロックを示しています。 pthread_exit(0);が_return 0;_に置き換えられた場合、5つのブロックは割り当てられません。

ただし、多数のスレッドの作成と結合をテストすると、終了時に使用されている未解放のメモリの量がnot増加することがわかります。これと、それがまだ到達可能であるという事実は、glibc実装の奇妙さだけを見ていることを示しています。いくつかのglibc関数は、最初に呼び出されたときにmalloc()を使用してメモリを割り当てます。これらの関数は、プロセスの存続期間の残りの間割り当てられ続けます。 glibcはプロセスの終了時にこのメモリを解放する必要はありません。プロセスがとにかく取り壊されていることを知っているからです。CPUサイクルの無駄になるだけです。

41
caf

まだこれに興味があるかどうかはわかりませんが、現在、同様の状況をデバッグしています。 pthread_exitを使用するスレッドは、valgrindに到達可能なブロックを報告させます。その理由はここでかなりよく説明されているようです:

https://bugzilla.redhat.com/show_bug.cgi?id=483821

基本的に、pthread_exitdlopenを引き起こしているようです。これは、プロセスの終了時に明示的にクリーンアップされることはありません。

11
Steven S

これは、exit()(および、明らかに、pthread_exit())を呼び出すと、自動的に割り当てられた変数が割り当てられたままになります。正しく巻き戻すには、戻るか投げる必要があります。

STL文字列でのC++ valgrindリークの可能性

@Klaim:その文書が私が間違っていると言っているところはわかりませんが、もしそうならそれは間違っています。 C++標準(§18.3/ 8)を引用するには、「exit()を呼び出しても自動オブジェクトは破棄されません。」 – James McNellis、10月10日19:11

"pthread_exit(0)"の代わりに "return 0"を実行することで問題が解決されたように見えるので(そして私のおかげで)、2つの動作は似ていると想定しています。

1
Dustin Oprea

Valgrindは、結合可能なスレッドの状態に割り当てられているストレージを追跡するのが難しいという経験があります。 (これはcafが示すのと同じ方向になります。)

常に0の値を返すようですので、おそらくアプリケーションの観点からスレッドに参加する必要があると思いますか?その場合、最初から切り離してそれらを起動することを検討すると、そのメモリの割り当てが回避されます。

欠点は、次のいずれかです。

  1. mainの最後に独自のバリアを実装します。事前にスレッド数がわかっている場合は、静的に割り当てられた単純なpthread_barrierで十分です。
  2. または、mainpthread_exitで終了して、まだ完了していない可能性がある残りの実行中のスレッドを強制終了しないようにします。
0
Jens Gustedt

あなたは実際にC++を使用していますか?明確にするために、ソースファイルは.c拡張子で終了し、g++ではなくgccでコンパイルしていますか?

関数が戻ったときに自動的にクリーンアップされると予想されるリソースを関数が割り当てている可能性はかなり高いようです。 std::vectorstd::stringのようなローカルC++オブジェクトはこれを行い、pthread_exitを呼び出した場合、それらのデストラクタはおそらく実行されませんが、戻っただけではクリーンアップされます。

私の好みは、pthread_exitなどの低レベルAPIを避け、可能な場合は常にスレッド関数から戻るだけです。これらは同等ですが、pthread_exitは、使用している言語をバイパスする事実上のフロー制御構成ですが、returnはそうではありません。

0
Doug