20

RPCにZeroCのIceライブラリを使用するC++共有ライブラリがあり、Iceのランタイムをシャットダウンしない限り、子プロセスがランダムなミューテックスにぶら下がっているのを観察しました。Iceランタイムはスレッドを開始し、多くの内部ミューテックスを持ち、サーバーに対してファイル記述子を開いたままにします。

さらに、内部状態を保護するために、独自のミューテックスがいくつかあります。

共有ライブラリは何百もの内部アプリケーションで使用されているため、プロセスがfork()を呼び出すタイミングを制御できません。そのため、プロセスがforkしている間、Iceを安全にシャットダウンし、ミューテックスをロックする方法が必要です。

ミューテックスと内部状態の処理に関するpthread_atfork()のPOSIX標準を読む:

あるいは、一部のライブラリは、ライブラリ内のミューテックスと関連するすべての状態を既知の値(たとえば、イメージが最初に実行されたときの状態)に再初期化する子ルーチンのみを提供できた可能性があります。ただし、ミューテックスまたはロックがまだロックされている場合、実装はミューテックスおよびロックの* _init()および* _destroy()呼び出しに失敗することが許可されているため、このアプローチは不可能です。この場合、子ルーチンはミューテックスとロックを再初期化できません。

Linuxでは、このテストCプログラムは、子pthread_atfork()ハンドラーのpthread_mutex_unlock()からEPERMを返します。Linuxをコンパイルするには、PTHREAD_MUTEX_ERRORCHECKマクロに_NPを追加する必要があります。

このプログラムは、この優れたスレッドからリンクされています。

子のミューテックスのロックを解除または破棄することは技術的に安全または合法ではないため、ミューテックスへのポインタを用意してから、子にヒープ上に新しいpthread_mutex_tを作成させ、親のミューテックスをそのままにしておく方がよいと思います。小さなメモリリーク。

唯一の問題は、ライブラリの状態を再初期化する方法であり、pthread_once_tをリセットすることを考えています。おそらく、POSIXにはpthread_once_tの初期化子があり、初期状態にリセットできるためです。

#include <pthread.h>
#include <stdlib.h>
#include <string.h>

static pthread_once_t once_control = PTHREAD_ONCE_INIT;

static pthread_mutex_t *mutex_ptr = 0;

static void
setup_new_mutex()
{
    mutex_ptr = malloc(sizeof(*mutex_ptr));
    pthread_mutex_init(mutex_ptr, 0);
}

static void
prepare()
{
    pthread_mutex_lock(mutex_ptr);
}

static void
parent()
{
    pthread_mutex_unlock(mutex_ptr);
}

static void
child()
{
    // Reset the once control.
    pthread_once_t once = PTHREAD_ONCE_INIT;
    memcpy(&once_control, &once, sizeof(once_control));
}

static void
init()
{
    setup_new_mutex();
    pthread_atfork(&prepare, &parent, &child);
}

int
my_library_call(int arg)
{
    pthread_once(&once_control, &init);

    pthread_mutex_lock(mutex_ptr);
    // Do something here that requires the lock.
    int result = 2*arg;
    pthread_mutex_unlock(mutex_ptr);

    return result;
}

上記のchild()のサンプルでは、​​PTHREAD_ONCE_INITで初期化された新しいpthread_once_tのコピーを作成することによってのみpthread_once_tをリセットします。新しいpthread_mutex_tは、ライブラリ関数が子プロセスで呼び出された場合にのみ作成されます。

これはハッキーですが、おそらくこの標準の幅木に対処するための最良の方法です。pthread_once_tにミューテックスが含まれている場合、システムはPTHREAD_ONCE_INIT状態からミューテックスを初期化する方法を持っている必要があります。ヒープに割り当てられたミューテックスへのポインタが含まれている場合は、新しいミューテックスを割り当てて、pthread_once_tにアドレスを設定する必要があります。私はそれがこれを打ち負かす特別な何かのためにpthread_once_tのアドレスを使用しないことを望んでいます。

comp.programming.threadsグループでpthread_atfork()を検索すると、多くの優れた議論と、この問題を解決するためにPOSIX標準が実際に提供するものがいかに少ないかがわかります。

また、pthread_atfork()ハンドラーからのみ非同期シグナルセーフ関数を呼び出す必要があるという問題もあります。最も重要なのは、memcpy()のみが実行される子ハンドラーであるように見えます。

これは機能しますか?共有ライブラリの要件に対処するためのより良い方法はありますか?

4

2 に答える 2

24

おめでとうございます。標準に欠陥が見つかりました。pthread_atfork子のハンドラーはミューテックスに対して操作を実行することが許可されていないため、ミューテックスで解決するために作成された問題を根本的に解決できません。

  • 呼び出し元は新しく作成された子プロセスの新しいメイン スレッドになり、ロックを取得した (親の) スレッドと同じスレッドではないため、ロックを解除できません。
  • それらはロックされているため、破壊することはできません。
  • それらは破棄されていないため、再初期化できません。

潜在的な回避策の 1 つは、ここでミューテックスの代わりに POSIX セマフォを使用することです。セマフォには所有者がいないため、親プロセスがセマフォをロックすると ( sem_wait)、親プロセスと子プロセスの両方がsem_post未定義の動作を呼び出すことなくそれぞれのコピーをロック解除できます ( )。

余談ですが、これsem_postは async-signal-safe であるため、子供が使用することは間違いなく合法です。

于 2011-07-07T03:30:20.913 に答える
9

これは、fork()を呼び出すプログラムのバグだと思います。マルチスレッドプロセスでは、子プロセスは非同期シグナルセーフ関数のみを呼び出す必要があります。プログラムがexecなしでフォークしたい場合は、スレッドを作成する前にフォークする必要があります。

スレッド化されたfork()/ pthread_atfork()の実際には良い解決策はありません。一部のチャンクは機能しているように見えますが、これは移植性がなく、OSのバージョン間で破損する可能性があります。

于 2010-09-10T23:02:27.280 に答える