web-dev-qa-db-ja.com

Unixのpthreadとシグナル:スレッドごとのシグナルハンドラー

スレッドが正しい信号をキャッチするのに問題があります。

例えば、

最初にメインスレッドを開始します(tid1)。

次に、signal(2)を使用して、_SIGUSR1_のシグナルハンドラーをfunction1()に設定します。

メインスレッドは、tid2で新しいスレッドを作成します。

スレッド2では、function2()を使用して_SIGUSR1_のシグナルハンドラーをsignal(2)に登録します。

次に、スレッド1はスレッド3(tid 3)を作成します。

スレッド3から、pthread_kill(1, SIGUSR1)を使用してスレッド1にシグナルを送信します。

ただし、function2()ではなく、function1()が呼び出されます。

この動作は意図されたものですか、それともこれらのシグナルハンドラーを機能させるために変更する必要があるものがありますか?

編集:少しデバッグを行ったところ、シグナルISがスレッド1に送信されていますが、何らかの理由でfunction2()がスレッド1から呼び出されていることがわかりました。 。これに対する回避策はありますか?

16
tomKPZ

に加えて アルクの答え

スレッドごとの関数ポインターを使用して、特定のシグナルが配信されたときに実行される関数をスレッドごとに選択できます。

注:シグナルは、その配信を明示的にブロックしていないanyスレッドに配信されます。これはそれを変えません。シグナルを特定のスレッドに送るには、pthread_kill()または同様のメカニズムを使用する必要があります。 (特定のスレッドではなく)プロセスに発生または送信されたシグナルは、(それをブロックしないものの中で)ランダムなスレッドによって引き続き処理されます。

個人的にこのアプローチを好むユースケースは考えられません。これまでのところ、他の方法、より簡単でより良い方法が常にありました。したがって、実際のアプリケーションにこのようなものを実装することを検討している場合は、一歩下がってアプリケーションロジックを再検討してください。

ただし、この手法は可能であるため、次のように実装します。

#include <signal.h>

/* Per-thread signal handler function pointer.
 * Always use set_thread_SIG_handler() to change this.
*/
static __thread void (*thread_SIG_handler)(int, siginfo_t *, void *) = (void *)0;

/* Process-wide signal handler.
*/
static void process_SIG_handler(int signum, siginfo_t *info, void *context)
{
    void (*func)(int, siginfo_t *, void *);

#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
    func = __atomic_load_n(&thread_SIG_handler, __ATOMIC_SEQ_CST);
#else
    func = __sync_fetch_and_add(&thread_SIG_handler, (void *)0);
#endif

    if (func)
        func(signum, info, context);
}

/* Helper function to set new per-thread signal handler
*/
static void set_thread_SIG_handler(void (*func)(int, siginfo_t *, void *))
{
#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
    __atomic_store_n(&thread_SIG_handler, func, __ATOMIC_SEQ_CST);
#else
    void (*oldfunc)(int, siginfo_t *, void *);
    do {
        oldfunc = thread_SIG_handler;
    } while (!__sync_bool_compare_and_swap(&thread_SIG_handler, oldfunc, func));
#endif
}

/* Install the process-wide signal handler.
*/
int install_SIG_handlers(const int signum)
{
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_sigaction = process_SIG_handler;
    act.sa_flags = SA_SIGACTION;
    if (sigaction(signum, &act, NULL))
        return errno;
    return 0;
}

上記はpthreadを必要とせず、非常に堅牢で信頼性が高いため、気に入っています。使用するアトミックビルトインのスタイルを選択するためのプリプロセッサロジックがあることによる視覚的な混乱は別として、注意深く見れば、それも非常に簡単です。

GCC4.7以降はC++ 11のような __ atomic組み込み を提供し、古いGCCバージョンおよびその他のコンパイラ(ICC、Pathscale、Portland Group)は __ syncレガシー組み込み を提供します。 __threadkeyword スレッドローカルストレージは、現在のすべてのPOSIX-yシステムで同様に使用できるはずです。

古風なシステムを使用している場合、または標準への準拠を主張している場合、次のコードはほぼ同等の動作をするはずです。

#include <pthread.h>
#include <signal.h>
#include <errno.h>

static pthread_key_t  thread_SIG_handler_key;

static void process_SIG_handler(int signum, siginfo_t *info, void *context)
{
    void (*func)(int, siginfo_t *, void *);

    *((void **)&func) = pthread_getspecific(thread_SIG_handler_key);
    if (func)
        func(signum, info, context);
}

static int set_thread_SIG_handler(void (*func)(int, siginfo_t *, void *))
{
    sigset_t block, old;
    int result;

    sigemptyset(&block);
    sigaddset(&block, SIG); /* Use signal number instead of SIG! */
    result = pthread_sigmask(SIG_BLOCK, &block, &old);
    if (result)
        return errno = result;

    result = pthread_setspecific(thread_SIG_handler_key, (void *)func);
    if (result) {
        pthread_sigmask(SIG_SETMASK, &old, NULL);
        return errno = result;
    }

    result = pthread_sigmask(SIG_SETMASK, &old, NULL);
    if (result)
        return errno = result;

    return 0;
}

int install_SIG_handlers(const int signum)
{
    struct sigaction act;
    int result;

    result = pthread_key_create(&thread_SIG_handler_key, NULL);
    if (result)
        return errno = result;

    sigemptyset(&act.sa_mask);
    act.sa_sigaction = process_SIG_handler;
    act.sa_flags = SA_SIGACTION;
    if (sigaction(signum, &act, NULL))
        return errno;

    return 0;
}

私が実際に使用したこのようなコードに相当する最も近い実際のコードは、1つのリアルタイム信号を使用したものだと思います(SIGRTMIN+0)リフレクターとして、1つのスレッドを除くすべてでブロックされました:別のリアルタイム信号を送信しました(SIGRTMIN+1)ブロッキングI/Oを中断するために、多数のワーカースレッドに送信します。 (単一のリアルタイム信号でこれを行うことは可能ですが、2信号モデルは実装が簡単で保守も簡単です。)

このような信号反射またはファンアウトが役立つ場合があり、このアプローチとそれほど違いはありません。ただし、誰かが興味を持っている場合は、独自の質問を正当化するのに十分な違いがあります。

10
Nominal Animal

「スレッドごとの」シグナルハンドラーをインストールすることはできません。

から man 7 signal (私による強調):

シグナルの処理はプロセスごと属性です。マルチスレッドアプリケーションでは、特定のシグナルの処理はすべてのスレッドで同じです。

ただし、「スレッドごと」に任意の数のシグナルタイプの受信をマスクすることにより、各シグナルタイプを異なるスレッドに向けることができます。

シグナルタイプのセットを特定のスレッドに向ける方法については、この回答をご覧ください: https://stackoverflow.com/a/20728819/694576

6
alk