C言語でプログラムを開発していると、処理の途中で一定時間だけ実行を停止させたい場面が多々あります。
例えば、ユーザーインターフェースの表示更新を調整したり、ネットワーク通信のリトライ間隔を設定したり、センサーデータの取得タイミングを制御したりする場合です。
「1秒待つ」という単純な動作であっても、実行環境がWindowsなのかLinuxなどのUnix系OSなのかによって、使用する関数やヘッダーファイルが異なります。
また、精度の高い待機が必要な場合には、マイクロ秒やナノ秒単位での制御が求められることもあります。
本記事では、C言語において1秒待機するための標準的な方法から、より高精度な待機を実現するためのテクニックまでを詳しく解説します。
POSIX環境(Linux/macOS)で1秒待機する方法
LinuxやmacOSなどのPOSIX準拠のOSで最も一般的に使われるのが、unistd.hヘッダーに定義されているsleep関数です。
この関数は引数に秒単位の数値を指定するだけで、非常に簡単にプログラムを停止させることができます。
sleep関数の基本的な使い方
sleep関数は、指定された秒数が経過するか、シグナルによって中断されるまで呼び出し元のスレッドを停止させます。
引数はunsigned int型であるため、1秒未満の端数を指定することはできない点に注意してください。
#include <stdio.h>
#include <unistd.h> // sleep関数のために必要
int main() {
printf("処理を開始します。\n");
// 1秒間待機します
sleep(1);
printf("1秒経過しました。次の処理に移ります。\n");
return 0;
}
処理を開始します。
(1秒間の待機)
1秒経過しました。次の処理に移ります。
より細かい制御ができるusleep関数
「1秒ではなく0.5秒(500ミリ秒)待ちたい」といった場合には、usleep関数が便利です。
usleepはマイクロ秒(100万分の1秒)単位で待機時間を指定するため、1秒待つ場合は1000000を指定します。
ただし、usleepは現在のPOSIX規格では非推奨(廃止予定)とされており、後述するnanosleepの使用が推奨されています。
#include <stdio.h>
#include <unistd.h>
int main() {
printf("500ミリ秒待機します。\n");
// 500,000マイクロ秒 = 0.5秒
usleep(500000);
printf("待機が終了しました。\n");
return 0;
}
ナノ秒単位の精度を持つnanosleep関数
より現代的で高精度な待機を実現するには、nanosleep関数を使用するのが最も適切な選択です。
この関数はtime.hヘッダーに定義されており、秒単位とナノ秒単位を組み合わせた構造体を利用します。
nanosleepの構造と引数
nanosleep関数は、struct timespecという構造体を用いて待機時間を設定します。
この構造体には、秒を保持するtv_secと、ナノ秒を保持するtv_nsecの2つのメンバが含まれています。
#include <stdio.h>
#include <time.h>
int main() {
struct timespec ts;
// 1秒と0ナノ秒を設定
ts.tv_sec = 1;
ts.tv_nsec = 0;
printf("nanosleepによる1秒待機を開始します。\n");
// 第一引数に待機時間、第二引数に残り時間を格納する変数を指定(NULL可)
if (nanosleep(&ts, NULL) != 0) {
perror("nanosleep");
return 1;
}
printf("待機が完了しました。\n");
return 0;
}
シグナル中断への対応
nanosleepの利点は、実行中にシグナルによって中断された場合、残りの待機時間を取得して再試行できる点にあります。
プログラムの堅牢性を高めるためには、中断された場合でも予定の時間をしっかり待つループ処理を組むのが理想的です。
nanosleepがシグナルで中断されると-1を返し、errnoにEINTRがセットされます。
Windows環境で1秒待機する方法
Windows環境(Visual Studioなど)では、POSIXのunistd.hは標準で利用できません。
その代わりに、Windows APIが提供するSleep関数を使用します。
Sleep関数の特徴
WindowsのSleep関数は、大文字から始まることと、引数の単位がミリ秒であることが大きな特徴です。
1秒待機させたい場合は、1000ミリ秒を指定する必要があります。
#include <stdio.h>
#include <windows.h> // Windows APIを利用するために必要
int main() {
printf("Windows環境で1秒待機します。\n");
// 1000ミリ秒 = 1秒
Sleep(1000);
printf("終了しました。\n");
return 0;
}
クロスプラットフォームを考慮した実装
WindowsとLinuxの両方で動作するプログラムを作る場合、プリプロセッサ(#ifdef)を使って関数を切り替えるのが一般的です。
以下のコードのように定義しておくことで、OSを意識せずに待機処理を記述できます。
#ifdef _WIN32
#include <windows.h>
#define sleep_ms(ms) Sleep(ms)
#else
#include <unistd.h>
#define sleep_ms(ms) usleep((ms) * 1000)
#endif
// 使用例: sleep_ms(1000); // 1秒待機
C11標準のthrd_sleepによるポータブルな待機
C言語の規格であるC11からは、スレッド機能の一部としてthrd_sleepという関数が導入されました。
これを利用すれば、OSごとの条件分岐を書かずにポータブルな待機処理を実装できる可能性があります。
thrd_sleep関数の使い方
thrd_sleepを利用するには、threads.hをインクルードする必要があります。
ただし、コンパイラや標準ライブラリ(特に古いMSVCや一部の軽量libc)によっては、threads.hをサポートしていない場合があるため注意が必要です。
#include <stdio.h>
#include <threads.h> // C11標準のスレッドライブラリ
int main() {
struct timespec ts;
ts.tv_sec = 1;
ts.tv_nsec = 0;
printf("C11標準関数で1秒待機します。\n");
// 戻り値0で正常終了
thrd_sleep(&ts, NULL);
printf("完了しました。\n");
return 0;
}
待機関数を選択する際の比較ポイント
用途に合わせて最適な関数を選ぶために、それぞれの特性をまとめました。
| 関数名 | 主な環境 | 最小単位 | 備考 |
|---|---|---|---|
sleep | POSIX (Linux/macOS) | 秒 | 最もシンプルだが精度が低い。 |
usleep | POSIX (旧規格) | マイクロ秒 | 非推奨。nanosleepへの移行が推奨される。 |
nanosleep | POSIX (現代) | ナノ秒 | 高精度。シグナル中断にも対応可能。 |
Sleep | Windows | ミリ秒 | Windows開発では必須。頭文字は大文字。 |
thrd_sleep | C11標準 | ナノ秒 | 環境依存が少ないがサポート状況に注意。 |
精度に関する注意点
プログラムで指定した「1秒」は、厳密には「少なくとも1秒」という意味になります。
OSのスケジューラの影響を受けるため、指定した時間よりもわずかに長く待機されることが一般的です。
リアルタイム性が極めて重要なシステムを除き、通常のアプリケーション開発ではこれらの関数の誤差は許容範囲内に収まります。
よくある質問とエラー対策
待機処理を実装する際に初心者が陥りやすいポイントについても触れておきます。
「sleepが認識されない」というコンパイルエラーが出る
OSに合わせた適切なヘッダーファイルがインクルードされているか確認してください。
Linux環境であれば<unistd.h>、Windows環境であれば<windows.h>が必要です。
待機中にCPU使用率が上がってしまうのでは?
今回紹介した関数(sleepやnanosleepなど)は、「ブロッキング」と呼ばれる動作をします。
CPUは他のプロセスの処理に回されるため、待機中に無駄な計算リソースを消費することはありません。
一方で、whileループで現在の時刻を監視し続ける「ビジーウェイト」という手法もありますが、こちらはCPU負荷を100%近くまで引き上げてしまうため、特別な理由がない限り避けるべきです。
1秒待っている間にキー入力を受け付けたい
sleep系の関数を呼ぶと、そのスレッドは完全に停止してしまいます。
もし待機中もユーザーの入力を受け付けたい場合は、マルチスレッド化するか、select関数やpoll関数を用いたタイムアウト待ちの手法を検討する必要があります。
まとめ
C言語で1秒待機する方法は、ターゲットとする実行環境によって異なります。
LinuxやmacOSであればsleep(1)やnanosleepを、WindowsであればSleep(1000)を使用するのが基本です。
単純な待ち時間であれば秒単位の関数で十分ですが、精密な制御が必要な場合はナノ秒単位をサポートする構造体ベースの関数を活用しましょう。
プログラムの移植性を高めたい場合は、プリプロセッサによる条件分岐やC11標準の関数を取り入れるのがスマートな解決策です。
それぞれの関数の特性を正しく理解し、開発しているアプリケーションに最適な待機処理を実装してください。
