6. さらなる改造 1
これまでシグナルのイベントを処理するのは、すべてシグナルハンドラで行ってました。しかしシグナルハンドラで処理する以上、最適化問題およびリエントラント問題という制限がつきまといます。
ではシグナルハンドラを使わないでシグナルイベントを処理すれば問題は解決するのでしょうか。
実は、ある特定の条件下でこの問題を緩和することができます。その方法はスレッドとsigwaitを併用するやり方です。
6.1 スレッドとの併用
スレッドと併用する方法は下記の通りです。
- スレッドを生成する前に補足対象のシグナルイベントをマスクする。
- シグナルイベントを処理する専用スレッドを生成する。
- そのスレッドがシグナルイベントをsigwaitを使って見張る
このようにする事で、非同期シグナルセーフではない関数を呼んでも問題が無くなります。さらに親プロセスではsleep(3)系などのpause(2)を行ってる関数を呼んでも問題ありません。
/*
gcc -g -W -Wall signal_test_thread.c -o signal_test_thread -lpthread
*/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <signal.h> void * signal_handler( void * arg ); void * worker_thread ( void * arg ); int main( int argc, char ** argv ) { sigset_t ss ; pthread_t pt; int max_cnt; if( argc < 3 ) { return 0; } max_cnt = atoi( argv[1] ); /* SIGHUP,SIGINTのブロックマスク */ sigemptyset( &ss ); sigaddset( &ss, SIGHUP ); sigaddset( &ss, SIGINT ); sigprocmask( SIG_BLOCK, &ss, NULL ); /* SIGHUP,SIGINT処理用スレッド生成 */ pthread_create( &pt, NULL, &signal_handler, NULL ); pthread_detach( pt ); /* 実処理スレッド生成 */ pthread_create( &pt, NULL, &worker_thread, &max_cnt ); pthread_detach( pt ); sleep( atoi( argv[2] )); return 0; } void * signal_handler( void * arg ) { sigset_t ss ; int sig; ( void )arg; /* SIGHUP,SIGINTを待つ */ sigemptyset( &ss ); sigaddset( &ss, SIGHUP ); sigaddset( &ss, SIGINT ); while( 1 ) { if( sigwait( &ss, &sig )) { printf( "not SIGHUP,SIGINT signal!!\n" ); continue; } switch( sig ) { case SIGHUP: printf( "signal_handler 'SIGHUP' Path!! [%d]\n", sig ); break; case SIGINT: printf( "signal_handler 'SIGINT' Path!! [%d]\n", sig ); break; default: break; } } printf( "sigwait END!!\n" ); return NULL; } void * worker_thread( void * arg ) { int count; int max_cnt = *( int * )arg; /* 実処理 */ for( count = 0; count < max_cnt; count ++ ) { sleep( 1 ) ; printf( "timer - [%d]\n", count ); } return NULL; }
実行するとカウントを始めます。
$ ./signal_test_thread 100 200
実行中にCtrl+C押下、または異なるターミナル等で下記のコマンドを実行する事で、signal_test_threadのpidに対してSIGHUPを送ることにより、画面にメッセージが表示されます。
$ kill -SIGHUP `ps -ef | grep signal_test_thread | grep -v grep |awk '{print $2 }'`
signal_handlerはシグナルイベントを処理する専用スレッドです。signal_handler関数内のsigwait以降では、非同期シグナルセーフではない関数を呼び出しても問題無しというのがイベントハンドラと異なる所です。
しかし、スレッド内での処理なのでスレッドセーフな関数を使用しなくてはいけないという制限が発生します。また、スレッド内で扱う変数をスレッド外で扱う場合でも同期対策(mutex等)を取らなくてはいけません。
なお、sigwaitでは取得できない詳細情報を取得するための、sigwaitinfo版も記載します。こちらは送信元からシグナルと一緒に文字列も送信しています。正確にはポインタを受け取っていますが、プログラムではsigqueueを使い、スレッド側ではsigwaitinfoを使うことで詳細情報を取得できます。
/*
gcc signal_test_sigwaitinfo.c -o signal_test_sigwaitinfo -W -Wall-g -lpthread */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <signal.h> #include <errno.h> void * worker_thread ( void * arg ); void * signal_handler( void * arg ); int main( int argc, char ** argv ) { sigset_t ss; pthread_t pt1, pt2; int max_cnt; if( argc < 2 ) { return 0; } max_cnt = atoi( argv[1] ); sigemptyset( &ss ); sigaddset( &ss, SIGHUP ); sigaddset( &ss, SIGINT ); sigprocmask( SIG_BLOCK, &ss, 0 ); pthread_create( &pt1, 0, &signal_handler, 0 ); pthread_detach( pt1 ); pthread_create( &pt2, 0, &worker_thread, &max_cnt ); pthread_join( pt2, 0 ); pthread_cancel( pt1 ); return 0; } void * worker_thread( void * arg ) { int count; int max_cnt = *( int * )arg; union sigval val; val.sival_ptr = malloc( 256 ); for( count = 0; count < max_cnt; count ++ ) { sleep( 1 ) ; printf( "timer - [%d] [%d]\n", max_cnt, count ); sprintf( val.sival_ptr, "message from \'worker_thread\' count:[%d]", count ); sigqueue( getpid( ), SIGHUP, val ); } return 0; } void * signal_handler( void * arg ) { sigset_t ss; siginfo_t info; int sig; ( void )arg; sigemptyset( &ss ); sigaddset( &ss, SIGHUP ); sigaddset( &ss, SIGINT ); while( 1 ) { sig = sigwaitinfo( &ss, &info ); if( sig < 0 ) { printf( "error sigwaitinfo:[%d][%s]\n", errno, strerror( errno )); continue; } switch( sig ) { case SIGHUP: printf( "signal_handler 'SIGHUP' Path!! [%d] message:[%s]\n", sig, ( char * )info.si_ptr ); break; case SIGINT: printf( "signal_handler 'SIGINT' Path!! [%d]\n", sig ); break; default: break; } } printf( "sigwait END!!\n" ); return 0; }

awk '{print $2 }'`