selectを横から止める方法

ここでは、selectに登録しているファイルディスクリプタに対して入出力を行わず、かつ、timeoutを設定せずにselectを横から止める方法を説明します。

selectを横から止めたい時とは?

selectはブロッキングAPIです。 selectを利用すると、selectに登録したファイルディスクリプタが入力/出力可能になるか、タイムアウトが来るまでブロックし続けてしまいます。

例えばマルチスレッドプログラミングなどを行っているときに、片方のスレッドでselectを使ってずっと入力を待っていたとします。 もう片方のthreadで何らかの理由により、selectのループをbreakしたくなったときにselectを解除したくなる場合があるとします。 このようなときには、selectが行われているPIDに対してシグナルを送信するとselectはEINTRで抜けてきます。

説明だけではわかりにくいと思うので、次に示すサンプルコードをご覧下さい。

サンプルコード

下記サンプルコードは、selectに入るとずっとブロックし続けます。 ブロッキング状態を解除するためには、下記サンプルコードにUSR1シグナルを送信します。 シグナルハンドラが登録されていないシグナルを受け取ると、アプリケーションは終了してしまいます。 そのため、下記サンプルではprintfだけ行って何もしないシグナルハンドラを登録しています。 ハンドラを登録せずに、SIGUSR1をignoreするように書いても、同様の事はできます。

シグナルの送信には「kill」コマンドを使います。 Mac OS Xでは、「kill -s SIGUSR1 サンプルアプリPID」とするとUSR1シグナルがサンプルアプリに送信されます。 サンプルアプリのPIDを調べるには「ps」コマンド等を利用してください。


#include <stdio.h>
#include <signal.h>
#include <sys/select.h>

void
sigusr1_handler(int sig)
{
  printf("signal called\n");
}

int
main()
{
  fd_set readfds;
  int n;

  /* SIGUSR1のシグナルハンドラを設定 */
  signal(SIGUSR1, sigusr1_handler);

  FD_ZERO(&readfds);

  printf("before select\n");

  /* SIGUSR1が来るまでselectはblockし続けます */
  n = select(0, &readfds, NULL, NULL, NULL);
  printf("after select : %d\n", n);

  /* errnoがEINTRになっているのを確認して下さい */
  perror("after select");

  return 0;
}


最後に

ここでは、selectの抜け方の説明としてシグナル受信時のselectの挙動を説明しました。 しかし、一般的には故意にシグナルを使ってselectを抜けさせるよりも、selectとシグナルを同時に使っているプログラムでselectがEINTRを返す時の処理をちゃんと書く事に気をつけるという方が大事かも知れません。 なお、EINTRを返すシステムコールはselectだけではありません。 readやwriteもEINTRを返す場合があります。