6. さらなる改造 3
6.3 タイマー処理
プロセスによるシグナルハンドラを定義する場合、よく使っていたのがタイマ処理(SIGALARM)でした。しかしスレッドを用いる場合は前述したようにプロセス側でハンドラを使用するべきではありません。では、スレッドを使いつつアラーム処理を行いたい場合は、どうやればよいのでしょうか。この対策として、POSIXではSIGEV_THREADを用意しました。
指定された時間が過ぎると指定した関数が呼ばれます。その際、その指定された関数はスレッドに乗っかって呼ばれているようです。
この関数内ではスレッドセーフな処理のみに留めて置くべきでしょう。
/*
gcc signal_test_event_thread.c -o signal_test_event_thread-W -Wall -g -lpthread -lrt */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <signal.h> #include <time.h> #include <unistd.h> #include <sys/types.h> #include <wait.h> timer_t timer_id; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int counter = 0; void timer_thread( sigval_t val ); int main( ) { struct itimerspec ts; struct sigevent se; se.sigev_notify = SIGEV_THREAD; se.sigev_value.sival_ptr = &timer_id; se.sigev_notify_function = timer_thread; se.sigev_notify_attributes = 0; ts.it_value.tv_sec = 1; ts.it_value.tv_nsec = 0; ts.it_interval.tv_sec = 1; ts.it_interval.tv_nsec = 0; timer_create( CLOCK_REALTIME, &se, &timer_id ); timer_settime( timer_id, 0, &ts, 0 ); pthread_mutex_lock( &mutex ); pthread_cond_wait( &cond, &mutex ); pthread_mutex_unlock( &mutex ); return 0; } void timer_thread( sigval_t val ) { ( void )val; pthread_mutex_lock( &mutex ); if( ++ counter >= 5 ) { pthread_cond_signal( &cond ); } pthread_mutex_unlock( &mutex ); printf ("[%lx][%x][%p] Timer %d\n", pthread_self( ), val.sival_int, val.sival_ptr, counter); }
引数も渡せますし、スレッドの属性設定もできます(join/detach、デフォルトがdetach)が、ここは初期値が望ましいと思います。属性をjoinに指定してもpthread_tの値が分からないため、待つことができないからです。
6.4 シグナルハンドラとスレッドの連携
上記にてスレッドでハンドラを用意した方が自由度が上がり、管理も楽になる事が分かったかと思います。これは、シグナルハンドラで使用してもよい関数が極端に少なく、かつスレッドへの通知処理も限られているからです。
しかし場合によってはどうしてもプロセス側でシグナルハンドラを用意し、かつハンドラからスレッドへの通知機能が欲しい場合もあります。そこで、「非同期シグナルセーフ内」で使えそうな関数を調べてみると、sem_post(3)があることが分かります。
sem_post(3) はセマフォを操作する関数です。セマフォは、一般的なプロセス間通信を行うためのものです。プロセス間通信についての詳細な説明は避けますが、大きく分けて3つあります。
- FIFO、メッセージキューに代表される、メッセージパッシング
- mmap、shm等に代表される、共有メモリ
- そして条件変数やレコードロック、セマフォに代表される、同期処理
この3つは主に異なるプロセス間で同期処理を行う時に必要な機能です。
そして、セマフォは非同期シグナルの安全性が謳われているので(一部CPUでは未対応らしいです)、このセマフォをスレッドとハンドラで共有する事で、スレッドを操作できます。
/*
gcc signal_test_sem.c -o signal_test_sem -W -Wall -g -lpthread
*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <semaphore.h> #include <signal.h> #include <time.h> #include <sys/time.h> struct itimerval gl_itv; unsigned long gl_all_count; sem_t semaphore; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void * thread_func ( void * arg ); void make_sigaction ( int sig, void ( *signal_handle )( int sig )); void signal_handle ( int sig ); void timer_set ( int sec ); int main( int argc, char ** argv ) { pthread_t id[256]; int i; if( argc < 2 || atoi( argv[1] ) <= 0 ) { return 1; } sem_init( &semaphore, 0, 0 ); int count = atoi( argv[1] ); for( i = 0; i < count; i ++ ) { pthread_mutex_lock( &mutex ); pthread_create( &id[i], 0, thread_func, ( void * )&i ); } timer_set( 1 ); for( i = 0; i < count; i ++ ) { pthread_join( id[i], 0 ); } timer_set( 0 ); sem_destroy( &semaphore ); return 0; } void * thread_func( void * arg ) { int cnt = *( int * )arg; pthread_mutex_unlock( &mutex ); fprintf( stdout, "thread_func : [%d] sem_wait... \n", cnt ); sem_wait( &semaphore ); fprintf( stdout, "thread_func : [%d] release!! \n", cnt ); return 0; } void make_sigaction( int sig, void ( * signal_handle )( int sig )) { struct sigaction sa; sa.sa_handler = signal_handle; sa.sa_flags = SA_RESTART; sigemptyset( &sa.sa_mask ); sigaction( sig, &sa, 0 ); return; } void timer_set( int sec ) { gl_itv.it_value.tv_sec = sec; gl_itv.it_value.tv_usec = 0; gl_itv.it_interval.tv_sec = sec; gl_itv.it_interval.tv_usec = 0; setitimer( ITIMER_REAL, &gl_itv, 0 ); make_sigaction( SIGALRM, signal_handle ); } void signal_handle( int sig ) { ( void )sig; sem_post( &semaphore ); }
セマフォの機能の詳細な説明は避けますが、簡単に言うと、sem_wait(3)はセマフォのカウンタが「0」より大きい値になるまでサスペンドします。そして、「0」より大きくなったらサスペンドが解除され、「1」を引きます。sem_post(3)はセマフォのカウンタに1加えます。
このような方法で、プロセス間で同期処理を行うようにしています。pthread_mutex、pthread_cond等と比べると、直感的でかなり扱いやすい印象があると思います。ただし、pthread_cond_broadcastのような、セマフォでサスペンドしているプロセスやスレッド全員を一発で同時に起こすインターフェースがないので、そのような事がしたいときは自前で用意する必要があります。
以上から、スレッドでシグナルハンドラを実装した方がよいということが分かるかと思います。シグナルを扱わなければならないプログラムは、上記のマルチスレッドプログラムに書き換えてしまうのもいい方法ではないでしょうか。

-W -Wall -g -lpthread -lrt */