類似の機能について
類似した機能として、SIGALRMシグナルイベントの利用と、timer_create(2)の利用があります。
SIGALRMシグナルイベント
以下は、SIGALRMを利用したプログラムです。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
void sig_handler( int sig_no );
int limit_loop = 0;
int loop_count = 0;
int main( int argc, char ** argv ) {
struct sigaction sa;
if( argc < 4 ) {
fprintf( stderr, "Usage: %s <num> <num> <num>...\n", argv[0] );
exit( EXIT_FAILURE );
}
struct itimerval val;
val.it_interval.tv_sec = atoi( argv[1] );
val.it_interval.tv_usec = 0;
val.it_value.tv_sec = atoi( argv[2] );
val.it_value.tv_usec = 0;
limit_loop = atoi( argv[3] );
setitimer( ITIMER_REAL, &val, 0 );
sa.sa_handler = sig_handler;
sigemptyset( &sa.sa_mask );
sigaction( SIGALRM, &sa, 0 );
while( limit_loop != loop_count ) {
pause( );
}
return 0;
}
void sig_handler( int sig_no ) {
( void )sig_no;
static int sec = 0;
printf( "%2d count passed.\n", sec );
sec ++;
loop_count ++;
}
setitimer(2)を利用して、定期的にSIGALRMを発行させます。第1引数にITIMER_REALを設定することで、SIGALRMが呼ばれるようになります。更に、SIGALRMハンドラを用意して、SIGALRMが呼ばれた時の処理を行います。
この方法の場合は、利用できるシグナルハンドラはSIGALRMのみなので、SIGALRM用のハンドラしか用意できず、必然的に1つの処理しか実装できません。アラームを利用した処理が1つしかない場合は、この方法はシンプルでよいと思います。
timer_createによるタイマー
もう1つ、timer_create(2)を利用したアラーム処理です。こちらの場合はシグナルハンドラではなくコールバック関数を登録し、あらかじめ設定された時間が経過するとコールバック関数が非同期(具体的にはスレッドかシグナルハンドラ形式)で呼ばれます。どちらで呼ばれたいかはイベントを設定するパラメータで決まります。
以下のサンプルソースはシグナル版です。スレッド版(timer_create_by_thread.c)はソースをダウンロードして参照ください。
#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 <sys/time.h>
#include <wait.h>
#define SIGIO_READ SIGRTMIN + 4
int cnt = 0;
#define TV2SEC(tv) ((double)((tv).tv_sec) + (double)((tv).tv_usec / 1000000.0))
void timer_handle( int signo, siginfo_t * info, void * ctx );
int main( int argc, char ** argv ) {
if( argc < 2 ) {
exit( 1 );
}
struct sigevent ev;
ev.sigev_notify = SIGEV_SIGNAL;
ev.sigev_signo = SIGIO_READ;
struct sigaction sa;
sigemptyset( &sa.sa_mask );
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_handle;
sigaction( SIGIO_READ, &sa, 0 );
struct itimerspec ts;
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_t timer_id;
timer_create( CLOCK_MONOTONIC,&ev, &timer_id );
timer_settime( timer_id, 0, &ts, 0 );
while( cnt < atoi( argv[1] )) {
sleep( 1 );
}
return 0;
}
void timer_handle( int signo, siginfo_t * info, void * ctx ) {
( void )signo;
( void )ctx;
if( info -> si_signo != SIGIO_READ ) {
return;
}
static struct timeval tv1,tv2;
gettimeofday( &tv2, 0 );
if( cnt == 0 ) {
printf( "timer_thread %d Path...\n", cnt + 1 );
}
else {
double rtn = TV2SEC( tv2 ) - TV2SEC( tv1 ) - 1;
printf( "timer_thread %d Path... [%f]\n", cnt + 1, rtn );
}
tv1 = tv2;
cnt ++;
}
スレッドの場合は単にコールバック関数を用意すれば良いですが、シグナルの場合はコールバック関数に加えて、シグナルハンドラを用意する必要があります。その際に使用するシグナルイベントは、SIGRTMINからSIGRTMAXまで(私の環境では32から65までの33個)です。
スレッドの場合、サーバの環境(メモリ等の資源)によって増減しますが、少なくとも数百個くらいの稼動は可能でしょう。その反面、スレッドの場合はコールバック関数を呼び出すための処理が、シグナルと比べて複雑です。
サーバが高負荷状態の時、スレッドの場合は遅延が発生しやすいでしょう。シグナルの場合はカーネルの作り上シンプルな構成のため、遅延が発生しにくいです。この辺りの動作の違いによる高負荷時の性能については、本章の趣旨から外れるため、別のレポートで改めて解析させて下さい。
