epoll
上記サンプルのように、管理対象のディスクリプタが2個と少ない場合、かつ動的に増えたり減ったりしない場合はselect(2)やpoll(2)の使用で結構でしょう。しかしソケットディスクリプタを使用するなど、大量かつ動的にディスクリプタが増減するような場合、select(2)やpoll(2)では性能上の問題があると言われています。
上記の通り、select(2)は管理可能なディスクリプタ数に上限がありますが、それ以外にも、select(2)やpoll(2)は、ready状態になったディスクリプタが一つでも存在した時、全ディスクリプタを対象に走査する必要があります。加えて作り上の問題で内部で作成しているディスクリプタ管理テーブルの作成と破棄を繰り返すことで処理が重くなるからだそうです(注4)。
上記のような問題を解決するためにはepoll(7)を利用します。epoll(7)では管理するディスクリプタにデータが届くと、操作対象のディスクリプタのみ返されるので、処理すべきディスクリプタだけを処理でき、無駄なサーチがなくなります。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/wait.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <sys/param.h>
void sigchld_ign( );
int timerfd( );
void child_process( int efd, int child_cnt );
int main(int argc, char ** argv ) {
struct epoll_event ev, evs[2];
uint64_t read_cnt;
int epfd, nfds, efd, tfd, i;
pid_t pid;
if( argc < 2 ) {
fprintf( stderr, "Usage: %s <num>\n", argv[0] );
exit( EXIT_FAILURE );
}
sigchld_ign( );
efd = eventfd( 0, 0 );
tfd = timerfd( );
if(( pid = fork( )) == 0 ) {
child_process( efd, atoi( argv[1] ));
}
printf( "Parent read to efd. pid:[%d] ppid:[%d]\n", getpid(), getppid());
epfd = epoll_create( 2 );
ev.data.fd = efd;
ev.events = EPOLLIN;
epoll_ctl( epfd, EPOLL_CTL_ADD, efd, &ev );
ev.data.fd = tfd;
ev.events = EPOLLIN;
epoll_ctl( epfd, EPOLL_CTL_ADD, tfd, &ev );
while( 1 ) {
nfds = epoll_wait( epfd, evs, 2, 1000 );
if( nfds == 0 ) {
kill( -pid, 0 );
if( errno == ESRCH ) {
break;
}
continue;
}
for( i = 0; i < nfds; i ++ ) {
if( evs[i].data.fd == efd ) {
read( efd, &read_cnt, sizeof( uint64_t ));
printf( "eventfd Parent read:[%ld]\n", read_cnt );
}
if( evs[i].data.fd == tfd ) {
read( tfd, &read_cnt, sizeof( uint64_t ));
printf( "timerfd Parent read:[%ld]\n", read_cnt );
}
}
}
printf( "Parent completed read loop\n" );
return( 0 );
}
・・・(以降は、select プログラムと同じ)
この件は少し前にC10K問題として取り上げられており、そこでもselect(2)のディスクリプタ管理数の限界、およびpoll(2)の遅延について論じられています。
