待機処理は、一定時間だけプログラムの実行を止めたいときに頻繁に使います。
本記事では、秒単位のsleepと、C11標準のthrd_sleepでミリ秒まで指定する方法を、環境差や注意点を含めて丁寧に解説します。
実用的なサンプルと出力も用意しました。
C言語で指定時間の待機
違いと選び方
秒単位だけでよく、UNIX系(LinuxやmacOS)が対象ならsleepが簡単です。
関数は秒を受け取り、OSにスレッドの休止を依頼します。
一方、ミリ秒や細かい時間を扱いたい、かつ標準Cで書きたいならthrd_sleep(C11)が第一候補です。
struct timespecで秒とナノ秒を指定でき、割り込み発生時の残り時間も受け取れます。
ただし注意として、sleepはPOSIXの関数で標準Cには含まれず、thrd_sleepはC11でも実装が欠ける処理系がある点を押さえてください。
Windows向けにはSleep(ミリ秒)という別APIがあります。
必要なヘッダ
各関数の宣言ヘッダや基本仕様を整理します。
| 機能 | 関数名 | ヘッダ | 時間単位 | 対応/備考 |
|---|---|---|---|---|
| 秒で待機(POSIX) | sleep | unistd.h | 秒 | UNIX系で広く利用。信号で中断されると残り秒数を返す。 |
| 高精度待機(C11) | thrd_sleep | threads.h, time.h | 秒+ナノ秒 | C11のスレッド系。実装がない処理系もある。 |
| 高精度待機(POSIX) | nanosleep | time.h | 秒+ナノ秒 | POSIX。Windowsでは通常不可。 |
| Windows API | Sleep | windows.h | ミリ秒 | Windows専用。戻り値なしで常に待つ。 |
標準Cだけで書くならthrd_sleep、POSIX前提ならsleepやnanosleep、Windows前提ならSleepという使い分けになります。
対応環境のポイント
Linux系や多くのUNIX系ならsleepは確実に使えます。thrd_sleepは新しめのlibcなら利用可能なことが多いです。
macOSではsleepは使えますが、threads.hは環境によって未提供の場合があります。その場合はnanosleepで代替できます。
Windows(Visual C++)は長らくthreads.h非対応でした。最新環境でも無い場合は素直にSleep(ミリ秒)を使うのが安全です。
C11スレッドが未サポートかどうかは__STDC_NO_THREADS__マクロの定義で検出できます。
sleepの使い方
基本の呼び出し方
sleepは秒単位の待機を行います。
戻り値は、要求時間ちょうど待てた場合は0、信号割り込みなどで途中で戻った場合は「残り秒数」です。
POSIX環境向けの最小例です。
// ファイル名: demo_sleep_basic.c
// コンパイル例(Linux/macOS):
// gcc demo_sleep_basic.c -o demo_sleep_basic
#include <stdio.h>
#include <unistd.h> // sleep(秒) の宣言 (POSIX)
int main(void) {
unsigned sec = 3;
printf("開始: %u秒待機します...\n", sec);
// 要求秒数を待機。戻り値は残り秒数(通常は0)
unsigned remaining = sleep(sec);
if (remaining == 0) {
puts("完了: 予定どおり待機しました。");
} else {
// 例: 信号により中断された場合
printf("注意: 割り込みにより残り%u秒で復帰しました。\n", remaining);
}
return 0;
}
開始: 3秒待機します...
完了: 予定どおり待機しました。
制限
sleepは標準CではなくPOSIX由来なので、Windowsの純正環境では使えません(MinGWなどPOSIX互換環境では使えることがあります)。
また、指定は秒のみでミリ秒以下には対応しません。
待機時間はOSスケジューラの都合で要求より長くなる可能性があり、sleep(0)は即時復帰します。
割り込みで戻る可能性があるため、厳密に指定時間「以上」待つ必要がある場合は、nanosleepやthrd_sleepで残り時間を繰り返し待機するのが堅実です。
thrd_sleepの使い方
基本の呼び出し方
thrd_sleepは、struct timespec(秒tv_sec+ナノ秒tv_nsec)で待機時間を指定します。
戻り値は0が成功、-1が割り込みなどで未完了で、その場合remainingに「残り時間」が返ります。
// ファイル名: demo_thrd_sleep_basic.c
// コンパイル例(Linux):
// gcc demo_thrd_sleep_basic.c -o demo_thrd_sleep_basic -pthread
// (glibcなどでthreads.hが提供される場合)
// 注意: いくつかの環境ではthreads.hが未提供です。
#include <stdio.h>
#include <time.h> // struct timespec
#include <threads.h> // thrd_sleep (C11)
int main(void) {
struct timespec ts;
ts.tv_sec = 2; // 2秒
ts.tv_nsec = 0; // 0ナノ秒
printf("2秒待機します...\n");
struct timespec rem; // 残り時間受け取り用
int rc = thrd_sleep(&ts, &rem);
if (rc == 0) {
puts("完了: 予定どおり待機しました。");
} else {
// 例: シグナル割り込みなどで中断された場合
printf("注意: 中断されました。残りは %lld秒 %ldナノ秒です。\n",
(long long)rem.tv_sec, rem.tv_nsec);
}
return 0;
}
2秒待機します...
完了: 予定どおり待機しました。
thrd_sleepはtimespecをそのまま相対時間として使います。
tv_nsecは0〜999,999,999に正規化しましょう。
ミリ秒で待機
ミリ秒指定を受け取ってthrd_sleepに渡すユーティリティを作るのが実用的です。
割り込み時は残り時間でリトライします。
// ファイル名: demo_thrd_sleep_ms.c
#include <stdio.h>
#include <time.h>
#include <threads.h>
// ミリ秒(ms)待機。成功で0、エラーで-1を返す。
// 非常に長いmsがtime_tに収まらない環境では-1を返す場合があります。
int sleep_ms_thrd(unsigned long ms) {
struct timespec req, rem;
req.tv_sec = (time_t)(ms / 1000UL);
unsigned long ms_rem = ms % 1000UL;
req.tv_nsec = (long)(ms_rem * 1000000L); // ms → ns
// 割り込みされたら残り時間で再試行
while (1) {
int rc = thrd_sleep(&req, &rem);
if (rc == 0) return 0; // 予定どおり待機完了
// rc != 0 → 中断。残り時間で続行
req = rem;
}
}
int main(void) {
puts("2500ミリ秒(2.5秒)待機します...");
if (sleep_ms_thrd(2500UL) == 0) {
puts("完了: 2.5秒待機しました。");
} else {
puts("エラー: 待機に失敗しました。");
}
return 0;
}
2500ミリ秒(2.5秒)待機します...
完了: 2.5秒待機しました。
秒とミリ秒の指定のコツ
整数ミリ秒からtimespecへの変換は、秒とナノ秒に分けて正規化します。例えば、1234msなら1秒+234,000,000nsです。
端数の丸め方に注意しましょう。浮動小数(例えば2.5秒)から変換する場合は、秒部分をfloorし、ナノ秒部分を四捨五入して1,000,000,000に達したら秒へ繰り上げます。
極端に大きい待機値はtime_tに収まらない可能性があります。安全策として長時間はループで分割して待つ方法が現実的です。
以下は、浮動小数秒を安全にtimespecへ変換して待機する例です。
// ファイル名: demo_thrd_sleep_seconds_double.c
#include <stdio.h>
#include <time.h>
#include <threads.h>
#include <math.h>
// 浮動小数の秒(例: 2.5)をthrd_sleepで待機
int sleep_seconds_double(double seconds) {
if (seconds <= 0.0) return 0;
// 秒と小数部に分解
double intpart;
double frac = modf(seconds, &intpart);
struct timespec req;
req.tv_sec = (time_t)intpart;
// 小数部をナノ秒に四捨五入
long nsec = (long)llround(frac * 1000000000.0);
// 1e9に達したら繰り上げ
if (nsec >= 1000000000L) {
req.tv_sec += 1;
nsec -= 1000000000L;
}
req.tv_nsec = nsec;
// 割り込み対応
struct timespec rem;
while (1) {
int rc = thrd_sleep(&req, &rem);
if (rc == 0) return 0;
req = rem;
}
}
int main(void) {
puts("2.5秒待機します...");
sleep_seconds_double(2.5);
puts("完了: 2.5秒待機しました。");
return 0;
}
2.5秒待機します...
完了: 2.5秒待機しました。
どの方法でも、実際の待機時間はスケジューラ遅延などで要求より長くなり得る点を前提にしてください。
使い分けの目安
ミリ秒が必要ならthrd_sleep
ミリ秒やサブ秒が必要ならthrd_sleepを第一選択にします。
C11標準なので、将来や他環境への移植性も高いです。
実装が無い場合はPOSIXのnanosleepかWindowsのSleepで代替しましょう。
秒だけならsleepで簡単
秒単位の待機でPOSIX環境が前提ならsleepが最も手軽です。
割り込み時には残り秒数で再試行するなど、必要な堅牢化だけ追加しましょう。
Windows対応が必要なときの選択
Windows専用ならSleep(ミリ秒)が明確です。
クロスプラットフォームを意識するなら、「利用可能ならthrd_sleep、ダメならnanosleepまたはSleep」という条件分岐のラッパー関数を用意すると移植が容易です。
以下は、ミリ秒待機の移植用ラッパーの例です。
// ファイル名: demo_portable_sleep_ms.c
// 目的: Windows/UNIX/C11にまたがってms待機を1関数で行う
// 使い方: portable_sleep_ms(ミリ秒)
#include <stdio.h>
#if defined(_WIN32)
#include <windows.h> // Sleep(ms)
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_THREADS__)
#include <time.h>
#include <threads.h> // thrd_sleep
#elif defined(_POSIX_C_SOURCE) || defined(__APPLE__)
#include <time.h> // nanosleep
#else
// 何も使えない環境
#endif
int portable_sleep_ms(unsigned long ms) {
#if defined(_WIN32)
// Windows: Sleepはミリ秒を受け取る。戻り値はないので常に成功とする。
// 非常に長い値はDWORDへ収まらない可能性があるため分割して待機。
while (ms > 0UL) {
DWORD chunk = (ms > 0xFFFFFFFFUL) ? 0xFFFFFFFFUL : (DWORD)ms;
Sleep(chunk);
ms -= (unsigned long)chunk;
}
return 0;
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_THREADS__)
// C11 threads: thrd_sleepでミリ秒を待機
struct timespec req, rem;
req.tv_sec = (time_t)(ms / 1000UL);
req.tv_nsec = (long)((ms % 1000UL) * 1000000L);
while (1) {
int rc = thrd_sleep(&req, &rem);
if (rc == 0) return 0; // 完了
req = rem; // 割り込み時は残り時間で続行
}
#elif defined(_POSIX_C_SOURCE) || defined(__APPLE__)
// POSIX: nanosleepで代替
struct timespec req, rem;
req.tv_sec = (time_t)(ms / 1000UL);
req.tv_nsec = (long)((ms % 1000UL) * 1000000L);
while (nanosleep(&req, &rem) != 0) {
req = rem; // 割り込み時は残り時間で続行
}
return 0;
#else
// 最低限のフォールバックがない環境では失敗
(void)ms;
return -1;
#endif
}
int main(void) {
puts("プラットフォーム非依存の方法で1500ms待機します...");
if (portable_sleep_ms(1500UL) == 0) {
puts("完了: 1.5秒待機しました。");
} else {
puts("エラー: この環境では移植ラッパーが利用できません。");
}
return 0;
}
プラットフォーム非依存の方法で1500ms待機します...
完了: 1.5秒待機しました。
このようなラッパーを1カ所にまとめると、利用側のコードは単純化でき、環境差の吸収が容易になります。
まとめ
秒だけで良いPOSIX環境ならsleep、ミリ秒まで欲しいなら標準C11のthrd_sleepが基本選択です。
WindowsではSleepを使い、クロスプラットフォームでは「thrd_sleep→nanosleep/Sleep」の順でフォールバックするラッパーが有効です。
割り込み時の残り時間で再試行する実装と、ミリ秒からtimespecへの正規化を身につければ、待機処理は安定して実装できます。
実際の待機時間はスケジューラの影響で伸びる可能性がある点を前提に、必要に応じて余裕を見込むと安心です。
