selectとepollとは
select
と epoll
は、いずれもでI/O多重化を行うための関数です。
使い方としては、複数のファイルディスクリプタの状態を監視し、どのファイルディスクリプタが読み込み可能、書き込み可能、またはエラー状態にあるかを判別するために使用されます。
select
select
の方がepoll
と比べて古い関数で、以下のような特徴があります。
特徴
- 使用法
-
ファイルディスクリプタの集合を3つのセット(読み取り、書き込み、例外)に分類し、それぞれのセットにファイルディスクリプタを追加して監視します。
select関数を呼び出すと、指定されたタイムアウト期間内にいずれかのファイルディスクリプタが状態変化を起こした場合に応答を返します。
- 制限
-
監視できるファイルディスクリプタの数がシステムによって制限されており、デフォルトでは
FD_SETSIZE
というマクロで決まります。通常は1024です。また、多くのファイルディスクリプタを監視する場合には非効率です。
- 移植性
-
古くからある関数のため、広くサポートされており、古いシステムでも動作します。
epoll
epoll
は、Linuxで提供されている新しいI/O多重化機構で、特に高いスケーラビリティを持つアプリケーションに適しています。
特徴
- 使用法
-
イベントを監視するための「監視セット」を作成し、このセットにファイルディスクリプタを追加・削除することができます。
epoll_wait
関数を使って、監視セットに対するイベントの発生を待ちます。 - 利点
-
- 膨大な数のディスクリプタを効率的に管理でき、特に大量の接続を処理するサーバーアプリケーションでパフォーマンスが向上します。
- エッジトリガー(イベントが一度発生したら通知)とレベルトリガー(イベントが発生し続ける限り通知)の2つのモードが利用可能です。
- 状態変化があった部分だけを通知するため、
select
のように全てのファイルディスクリプタを再スキャンする必要がありません。
- 制限
-
Linux固有の機能であり、移植性が低いため、他のUnix系システムやWindowsでは利用できません。
適用例
小規模なプロジェクトや古い環境であれば、select
の方が簡単で移植性も高いため、適している場合が多いです。
大量のソケットを扱うサーバーアプリケーションやパフォーマンスが求められる環境では、epoll
の方が効率的です。
実装例
2つのファイルディスクリプタを監視する簡単な実装例を、select
と epoll
それぞれで紹介します。
select
#include <sys/select.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd1 = 0; // 標準入力 (通常はFD 0)
int fd2 = 1; // 標準出力 (通常はFD 1)
fd_set readfds;
while (1) {
FD_ZERO(&readfds); // fd_setをクリア
FD_SET(fd1, &readfds); // fd1をセットに追加
FD_SET(fd2, &readfds); // fd2をセットに追加
int max_fd = fd1 > fd2 ? fd1 : fd2; // 最大のFDを取得
// selectで監視
int activity = select(max_fd + 1, &readfds, NULL, NULL, NULL);
if (activity < 0) {
perror("select error");
break;
}
if (FD_ISSET(fd1, &readfds)) {
char buffer[1024];
int bytes = read(fd1, buffer, sizeof(buffer) - 1);
if (bytes > 0) {
buffer[bytes] = '\0';
printf("fd1 (stdin) から読み込み: %s\n", buffer);
}
}
if (FD_ISSET(fd2, &readfds)) {
printf("fd2 (stdout) にイベント発生\n");
}
}
return 0;
}
epoll
#include <sys/epoll.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd1 = 0; // 標準入力 (通常はFD 0)
int fd2 = 1; // 標準出力 (通常はFD 1)
int epoll_fd = epoll_create1(0);
struct epoll_event event, events[2];
// fd1 を epoll に登録
event.events = EPOLLIN;
event.data.fd = fd1;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd1, &event);
// fd2 を epoll に登録
event.data.fd = fd2;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd2, &event);
while (1) {
int num_fds = epoll_wait(epoll_fd, events, 2, -1);
for (int i = 0; i < num_fds; i++) {
if (events[i].data.fd == fd1) {
char buffer[1024];
int bytes = read(fd1, buffer, sizeof(buffer) - 1);
if (bytes > 0) {
buffer[bytes] = '\0';
printf("fd1 (stdin) から読み込み: %s\n", buffer);
}
}
if (events[i].data.fd == fd2) {
printf("fd2 (stdout) にイベント発生\n");
}
}
}
close(epoll_fd);
return 0;
}