シグナル管理

マルチスレッド・プロセス内のシグナルは、従来の単一スレッド・プログラムのシグナルの拡張です。

マルチスレッド・プロセスにおけるシグナル管理は、プロセスとスレッドのレベルで共有され、次に挙げるものから成り立っています。
  • プロセスごとのシグナル・ハンドラー
  • スレッドごとのシグナル・マスク
  • 各シグナルの単一の送達

シグナル・ハンドラーとシグナル・マスク

シグナル・ハンドラーは、プロセス・レベルに維持されます。 シグナルを待つ場合は、 sigwait サブルーチンを使用することを強くお勧めします。 シグナル・ハンドラーのリストはプロセス・レベルで保守されており、プロセス内のスレッドはすべてそのリストを変更する可能性があるため、sigaction サブルーチンの使用は推奨されていません。 2 つのスレッドが同じシグナルにシグナル・ハンドラーを設定すると、 最後に sigaction サブルーチンを呼び出したスレッドが、前のスレッド呼び出しの設定を指定変更します。ほとんどの場合、スレッドがスケジュールされる順序を予想することはできません。

シグナル・マスクは、スレッド・レベルに維持されます。 送達をブロックされるシグナルの独自のセットを、各スレッドで持つことができます。 呼び出しスレッドのシグナル・マスクを取得および設定するには、 sigthreadmask サブルーチンを使用する必要があります。 sigprocmask サブルーチンは、予期しない動作が起こる可能性があるため、マルチスレッド・プログラムで使用してはなりません。

pthread_sigmask サブルーチンは sigprocmask サブルーチンとよく似ています。 両サブルーチンのパラメーターと使用法は、同じです。 既存のコードをスレッド・ライブラリーをサポートするために移植する場合は、 sigprocmask サブルーチンを pthread_sigmask サブルーチンで置き換えることができます。

シグナルの生成

特定のスレッドに起因する、 あるアクションによって生成されたシグナル (ハードウェア障害など) は、 そのシグナルの原因となったスレッドに送信されます。 プロセス ID、プロセス・グループ ID、または非同期イベント (端末アクティビティーなど) と関連して生成されたシグナルは、プロセスに送信されます。

  • pthread_kill サブルーチンは、スレッドにシグナルを送信します。 スレッド ID はプロセス内のスレッドを識別するものであるため、 このサブルーチンは同一プロセス内のスレッドだけにシグナルを送信できます。
  • kill サブルーチン (および kill コマンド) は、プロセスにシグナルを送信します。 スレッドは、以下の呼び出しを実行することによって、 そのプロセスに Signal シグナルを送信することができます。
    kill(getpid(), Signal);
  • raise サブルーチンは、コール側のスレッドのプロセスにシグナルを送るために使用することはできません。 raise サブルーチンは、次のような呼び出しを使用して、 コール側のスレッドにシグナルを送信します。
    pthread_kill(pthread_self(), Signal);
    こうすると、raise サブルーチンのコール側に、 確実にシグナルを送信することができます。 したがって、raise サブルーチンは通常コール側にシグナルを送信することを目的としているので、単一スレッド・プログラム用に作成されたライブラリー・ルーチンを簡単にマルチスレッド・システムに移植することができます。
  • alarm サブルーチンは、シグナルを後でプロセスに送信することを要求し、アラーム状態はプロセス・レベルで維持されます。 したがって、 最後に alarm サブルーチンを呼び出したスレッドがプロセス内のその他のスレッドの設定を指定変更します。 マルチスレッド・プログラムでは、SIGALRM シグナルは、alarm サブルーチンを呼び出したスレッドに必ず送達されるとは限りません。 コール側のスレッドが終了させられる場合もあるので、スレッドはシグナルを受信できません。

シグナルの処理

シグナル・ハンドラーは、シグナルの送達先のスレッド内で呼び出されます。 スレッド・ライブラリーによって、 次のような制限がシグナル・ハンドラーに加えられます。
  • シグナル・ハンドラーは、 SetJMP または sigsetjmp サブルーチンに対する対応する呼び出しが同じスレッドで実行される場合にのみ、 Longjmp または siglongjmp サブルーチンを呼び出すことができます。

    通常、シグナルを待機したいプログラムは、対応する setjmp サブルーチンが呼び出されるポイントで実行を継続するために、longjmp サブルーチンを呼び出すシグナル・ハンドラーをインストールします。 これをマルチスレッド・プログラムで実行することはできません。setjmp サブルーチンを呼び出したスレッド以外のスレッドにシグナルが送達されてしまい、誤ったスレッドでハンドラーが実行される可能性があるためです。

    注: シグナル・ハンドラーから longjmp を使用すると、未定義の動作になる可能性があります。
  • シグナル・ハンドラーから呼び出せる pthread ルーチンはありません。 シグナル・ハンドラーから pthread ルーチンを呼び出すと、 アプリケーションのデッドロックが発生する場合があります。

スレッドが非同期に生成されたシグナルを待機できるようにするために、スレッド・ライブラリーは sigwait サブルーチンを提供します。 sigwait サブルーチンは、 待機されたシグナルの 1 つがプロセスまたはスレッドに送信されるまで、 コール側のスレッドをブロックします。 sigwait サブルーチンを使用して待機されたシグナルに、シグナル・ハンドラーをインストールすることはできません。

一般に、プログラムは非同期に生成されたシグナルを待つ専用スレッドを作成することがあります。 このようなスレッドは、sigwait サブルーチン・コールでループしていて、 シグナルを取り扱います。 このようなスレッドですべてのシグナルをブロックすることが推奨されています。 次のコード・フラグメントは、そのようなシグナル待ちスレッドの例を示しています。
#include <pthread.h>
#include <signal.h>

static pthread_mutex_t mutex;
sigset_t set;
static int sig_cond = 0;

void *run_me(void *id)
{
       int sig;
       int err;
       sigset_t sigs;
       sigset_t oldSigSet;
       sigfillset(&sigs);
       sigthreadmask(SIG_BLOCK, &sigs, &oldSigSet);

       err = sigwait(&set, &sig);

       if(err)
       {
              /* do error code */
       }
       else
       {
              printf("SIGINT caught\n");
              pthread_mutex_lock(&mutex);
              sig_cond = 1;
              pthread_mutex_unlock(&mutex);
       }

       return;
}

main()
{
       pthread_t tid;

       sigemptyset(&set);
       sigaddset(&set, SIGINT); 
       pthread_sigmask(SIG_BLOCK, &set, 0);

       pthread_mutex_init(&mutex, NULL);

       pthread_create(&tid, NULL, run_me, (void *)1);

       while(1)
       {
              sleep(1);
              /* or so something here */

              pthread_mutex_lock(&mutex);
              if(sig_cond)
              {
                     /* do exit stuff */
                     return;
              }
              pthread_mutex_unlock(&mutex);

       }

}

複数のスレッドが sigwait サブルーチンを呼び出した場合、マッチング・シグナルが送信されると、 1 つの呼び出しだけが確実に戻ります。 どのスレッドが起こされるかは予測できません。 スレッドが sigwait を実行しようとすると同時に、sigwait を実行しないその他のシグナルを処理する場合、ユーザー定義のシグナル・ハンドラーは適切に処理するために sigwaiter シグナルをブロックする必要があります。 sigwait サブルーチンが取り消しポイントを提供することに注意してください。

専用スレッドは実際のシグナル・ハンドラーではないため、他のどのスレッドにも条件を通知することがあります。 特定のシグナルを待っているスレッドをすべて起こす sigwait_multiple ルーチンをインプリメントすることができます。 sigwait_multiple ルーチンのそれぞれの呼び出し元は、シグナルのセットを登録します。 その後、呼び出し元は条件変数を待ちます。 単一スレッドは、登録されているすべてのシグナルの結合で、 sigwait サブルーチンを呼び出します。 sigwait サブルーチンへの呼び出しが戻ると、適切な状態が設定されて、 条件変数がブロードキャストされます。 新しい呼び出し元が sigwait_multiple サブルーチンを呼び出すと、 保留状態の sigwait サブルーチン呼び出しが取り消されて再発行され、 待機されていたシグナルのセットを更新します。

シグナルの送達

シグナルは、アクションを無視するように設定されていないかぎり、スレッドに送達されます。 マルチスレッド・プロセスにおいて、シグナルを送達するときは次の規則が適用されます。

  • アクションがターゲットのスレッドまたはプロセスの終了、中止、 または継続に設定されているシグナルは、それぞれプロセスを (およびそのスレッド全体も) 終了、 中止、または継続します。 外部から見たシグナルの動作を変更せずに、単一スレッドのプログラムをマルチスレッド・プログラムに書き直すことができます。

    例えば、grep コマンドのようなマルチスレッドのユーザー・コマンドを考えてみましょう。 ユーザーは自分の好みのシェルでコマンドを開始して、その後、kill コマンドでシグナルを送信することでこの停止を決定することができます。 このシグナルで、grep コマンドを実行しているプロセス全体を停止する必要があります。

  • pthread_kill または raise サブルーチンを使用して特定のスレッドのために生成されたシグナルは、そのスレッドに送達されます。 スレッドがシグナルの送達をブロックしている場合、そのシグナルは、 送達のブロックが解除されるまで、そのスレッドで保留状態に設定されます。 シグナルが送達される前にスレッドが終了した場合、そのシグナルは無視されます。
  • 例えば、kill サブルーチンを使用してプロセスのために生成されたシグナルは、 プロセス内の 1 つのスレッドだけに送達されます。 1 つまたは複数のスレッドが sigwait サブルーチンを呼び出した場合、そのシグナルはこれらのスレッド中の 1 つだけに送達されます。 それ以外の場合、シグナルは、シグナルの送達をブロックしなかった 1 つのスレッドだけに送達されます。 これらの条件に合致するスレッドがない場合、シグナルは、 スレッドがこのシグナルを指定して sigwait サブルーチンを呼び出すか、またはスレッドがシグナルの送達のブロックを解除するまで、 そのプロセスで保留状態に設定されます。

(スレッドまたはプロセス上の) 保留シグナルと関連したアクションを無視するように 設定されている場合、そのシグナルは無視されます。