シグナルとスレッド:シグナルを特定スレッドに通知する
(プログラムの概要)
前回「標準シグナルとリアルタイムシグナル2」で作成したリアルタイムシグナルの改良版です。スレッドを作成し、シグナルの通知を作成したスレッドに限定させてシグナルハンドラーを動作させます。プログラムの動きは前回までと同様で、シグナルハンドラーが起動された回数を1秒おきに表示させます。
(注)今回も実験の為にシグナルハンドラー内で無駄な処理を行い重たくしてあります。

/* * sample program * signal handler / thread */ #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include <time.h> #include <unistd.h> #include <errno.h> #include <pthread.h> int sigcnt; void SignalHandler(int, siginfo_t*, void*); int _nanosleep(int, int); void thread1(void); int main(void) { pthread_t thread_id; sigset_t sigmask; printf("sample program(%s) start\n", __FILE__); /* create new thread */ if(pthread_create(&thread_id, NULL, (void *)thread1, NULL) < 0){ perror("pthread_create error"); exit(1); } /* mask SIGRTMIN + 1 */ sigemptyset(&sigmask); sigaddset(&sigmask, SIGRTMIN +1); if(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) != 0){ perror("pthread_sigmask error"); exit(1); } /* loop */ while(1){ _nanosleep(1, 0); /* sleep 1 sec */ printf("%d\n", sigcnt); } return 0; } void thread1(void) { struct sigaction action; struct sigevent evp; printf("thread1 start\n"); memset(&action, 0, sizeof(action)); memset(&evp, 0, sizeof(evp)); /* set signal handler */ action.sa_sigaction = SignalHandler; action.sa_flags = SA_SIGINFO | SA_RESTART; sigemptyset(&action.sa_mask); evp.sigev_notify = SIGEV_SIGNAL; evp.sigev_signo = SIGRTMIN + 1; if(sigaction(SIGRTMIN + 1, &action, NULL) < 0){ perror("sigaction error"); exit(1); } /* loop */ while(1){ _nanosleep(1, 0); /* sleep 1 sec */ } return; } void SignalHandler(int signum, siginfo_t *info, void *ctxt) { char mem1[1024]; int i = 0; sigcnt++; for(i = 0; i < 10000; i++){ memset(mem1, 0, sizeof(mem1)); } return; } int _nanosleep(int sec, int nsec) { struct timespec req, rem; req.tv_sec = sec; req.tv_nsec = nsec; rem.tv_sec = 0; rem.tv_nsec = 0; while(nanosleep(&req, &rem)){ if(errno == EINTR){ req.tv_sec = rem.tv_sec; req.tv_nsec = rem.tv_nsec; }else{ perror("nanosleep error"); return -1; } } return 0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
(プログラムの概要)
25L~29L:スレッドの作成
thread1()という関数をスレッドとして作成します。thread1()で前回までと同様にシグナルハンドラーの登録を行っています。
31L~37L:シグナルのマスク
今回の肝となる部分です、SIGRTMIN+1のシグナルをmain()関数のスレッドに通知しないようブロックしています。これによりSIGRTMIN+1の通知で起動されるSignaHandler()は全てthread1スレッドで動作するようになります。
以降の処理は前回までと同じで1秒に1回シグナルハンドラー起動回数を表示します。
#! /bin/bash #input : PID cnt=0 while [ $cnt -lt 10000 ]; do kill -35 $1 cnt=`expr $cnt + 1` done
1 2 3 4 5 6 7 8 9
引数として渡されるPIDのプロセスに対してSIGRTMIN+1を1万回送信します。
(注)実行した環境でのSIGRTMIN+1はSIG35です、環境によってシグナル番号が変わる可能性があります。

(動作)
gcc -Wall sigrt2.c -lpthread
-lpthreadオプションをつけてコンパイルします。
プログラムを起動させ、ps -aコマンドでプロセスIDを取得し、スクリプトを実行してみてください。
どうでしょう?今回は1秒に1回シグナルハンドラーの起動回数が表示され、スクリプトで指定した回数起動されると思います。
(別スレッドでシグナルを処理する)
実際のプログラムではシグナルハンドラーはできるだけ軽く作るべきであり、前回のように処理が追いつかないということはないと思います。しかし、周期タイマーのように正確性を求められるようなものは専用のスレッドを作成しシグナルを処理する方法も効果的だと思います。
(補足)
スレッドを複数作成した場合は通知したいスレッド以外の全てのスレッドでシグナルをマスクする必要があります。各スレッドでpthread_sigmask()のSIG_BLOCKを行っていたのでは面倒なので、スレッド起動前にsigprocmask()でプロセス全体にSIG_BLOCKを指定しておき、通知を行うスレッドにてpthread_sigmask()でSIG_UNBLOCKを行う方法がスマートです。
新しく生成されたスレッドは生成元のスレッドのシグナルマスクを継承するので、sigprocmask()の代わりにmain()関数でpthread_sigmask()のSIG_BLOCKを行っても同じ動きになります。

マルチスレッドについては排他制御が必要になりプログラム的に難しくなる、スタック等でメモリを食うなど嫌いな人も多いかもしれません。しかしCPUの進化の方向を考えると今後はマルチスレッドが必須であると考えられます。そこで次回は排他制御に関してまとめてみたいと思います。