スリープ処理
プログラムを組む上で、一定時間処理を停止したいということが出てくるだろう。 ここでは処理を指定時間停止させる、いわゆるスリープ系の処理について紹介する。
基本的な機能のように思えるが、残念ながら旧来のC言語標準関数の中にはこれに該当する機能はない。 C11 ではスレッドが言語仕様に取り込まれており、この中にスレッドをスリープさせる機能もある。 これがC言語標準のスリープAPIとも言えるが、 C11 が使える環境はまだ限られており、 中でもスレッドへの対応は遅れているようで GCC でも本稿執筆時点では実装されていない。 そのため現状では環境依存の API を使用する必要がある。
ここでは Linux をはじめとする POSIX 環境で使用できる関数について説明する。
処理を停止させる方法としてビジーループを含めればC言語標準で実現できると言えるが、多くの場合悪手である。 ビジーループが有用である場合ももちろんあるが、今回は除外する。
sleep 関数
手軽に利用できる秒単位のスリープ関数だ。 MAN も参照。
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
引数に指定するのは「秒」なので、細かい時間は指定できない。 プロセスがシグナルを受け取った場合など、指定時間未満で抜ける場合もあり、 その場合は戻り値として残りの秒数が返る。
手軽に使えるのだが、指定できる時間が秒単位になるので細かな制御はできない。
1秒間スリープするサンプルコードは以下となる。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
fprintf(stderr, "start\n");
sleep(1);
fprintf(stderr, "end\n");
return 0;
}
usleep 関数
手軽に利用できるマイクロ秒単位のスリープ関数だ。 ただし、現在は POSIX から削除されているため、利用すべきではない。 MAN も参照。
#include <unistd.h>
int usleep(useconds_t usec);
引数にはマイクロ秒を指定するため、sleep
よりも細かな制御ができる。
指定した時間がスケジューラの精度以下の場合は、その制度の数値に切り上げられる。
すでに POSIX から削除され、過去との互換性のために残されている関数なので、新規には利用すべきではないが、
手元で一度書いて捨ててしまうようなテストに使うのであればよいだろう。
useconds_t
型は 0 ~ 1000000 を表現する形のため、
1000000 以上を指定するとエラーとなるシステムもあるとのことなので注意しよう。
1秒間スリープするサンプルコードは以下となる。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
fprintf(stderr, "start\n");
usleep(1000000);
fprintf(stderr, "end\n");
return 0;
}
nanosleep 関数
POSIX で規定されている、高精度に時間を指定できるスリープ関数だ。 MAN も参照。
#include <time.h>
int nanosleep(const struct timespec *req, struct timespec *rem);
引数は struct timespec
でナノ秒単位でスリープ時間を指定することができる。
struct timespec {
long tv_sec; /* 秒 */
long tv_nsec; /* ナノ秒 */
};
時間を構造体を使用して指定する必要があるので、使用するのに一手間かかるが、その分高機能だ。
ナノ秒という非常に小さな単位での指定ができるが、 システム側の内部クロックや制御がそれだけの精度がない場合は、一番近い値に繰り上げられる。
またシグナルハンドラに割り込まれた場合など、指定時間未満で処理が戻ることがある。
その場合は、戻り値が -1 となり、
第二引数である rem
が NULL でなければ残り時間がここに格納される。
NULL であれば単純に無視されるので、残り時間は不要であれば NULL を指定すれば良い。
1秒間スリープするサンプルコードは以下となる。
#include <stdio.h>
#include <time.h>
int main(int argc, char **argv) {
struct timespec ts;
fprintf(stderr, "start\n");
ts.tv_sec = 1;
ts.tv_nsec = 0;
nanosleep(&ts, NULL);
fprintf(stderr, "end\n");
return 0;
}
clock_nanosleep 関数
POSIX で規定され、クロックを指定できる高精度なスリープ関数だ。 MAN も参照。
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *request,
struct timespec *remain);
nanosleep
と比較して引数が2つ増えている。
一つ目が click_id
で、
これは clock_gettime で説明しているものと同じクロックだ。
もう一つが flags
で取りうる値としては 0 か TIMER_ABSTIME
だ。
0 を指定した場合は、nanosleep
と同様に指定した時間はスリープする時間を意味する。
一方、TIMER_ABSTIME
を指定した場合は、スリープから抜ける実時間を意味する。
システム時計に合わせてスリープから抜ける時間を指定することができる。
flags
に 0 を指定した場合の、
1秒間スリープするサンプルコードは以下となる。
#include <stdio.h>
#include <time.h>
int main(int argc, char **argv) {
struct timespec ts;
fprintf(stderr, "start\n");
ts.tv_sec = 1;
ts.tv_nsec = 0;
clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
fprintf(stderr, "end\n");
return 0;
}
flags
に TIMER_ABSTIME
を指定した場合の、
1秒間スリープするサンプルコードは以下となる。
実時間を指定するため最初に時刻を読みだし、
それに1秒分加算した時点までのスリープという形で指定する。
#include <stdio.h>
#include <time.h>
int main(int argc, char **argv) {
struct timespec ts;
fprintf(stderr, "start\n");
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 1;
clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, NULL);
fprintf(stderr, "end\n");
return 0;
}
select 関数
スリープのための関数ではないが、スリープとして使うこともできる関数だ。 本来の使い方は複数のファイルディスクリプターの状態変化を監視するための関数だ。 MAN も参照。
ポイントは、 POSIX 環境だけでなく、 Windows 環境でも使えるという点だ。 MSDN も参照。
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
第一引数は監視するすべてのファイルディスクリプタの最大値に1を加算した値、
第二引数以降は、読み出し、書き込み、例外の監視をするファイルディスクリプタ集合を指定し、
最後に監視のタイムアウト時間を指定する。
監視対象のファイルディスクリプタに変化があるか、タイムアウト時間が経過すると戻ってくるという関数である。
タイムアウト時間は struct timeval
を使用するため、マイクロ秒単位まで指定することができる。
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* マイクロ秒 */
};
この関数に対して、第一引数に 0、第二引数から第四引数までを NULL とすると、 監視対象のファイルディスクリプタが一切ない状態になり、単にタイムアウトするまでスリープする動作となる。
本来スリープ目的ではない関数をこのように利用するのは、 移植性の高いスリープ関数として使えるという理由のようだが、実際どの程度の意味があるかは不明だ。 このような使い方もあるということで紹介しておく。
1秒間スリープするサンプルコードは以下となる。
#include <stdio.h>
#include <sys/select.h>
int main(int argc, char **argv) {
struct timeval tv;
fprintf(stderr, "start\n");
tv.tv_sec = 1;
tv.tv_usec = 0;
select(0, NULL, NULL, NULL, &tv);
fprintf(stderr, "end\n");
return 0;
}
以上、様々なスリープ方法について説明した。 はじめはこのような方法を知らないため、 スリープのために無駄な処理をするループを挟んだりしてしまうこともあるかもしれないが、 良い方法とはいえないので、これら関数を使うようにしよう。