閉じる

C言語のsleepとthrd_sleepの使い方まとめ(秒/ミリ秒対応)

待機処理は、一定時間だけプログラムの実行を止めたいときに頻繁に使います。

本記事では、秒単位の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)sleepunistd.hUNIX系で広く利用。信号で中断されると残り秒数を返す。
高精度待機(C11)thrd_sleepthreads.h, time.h秒+ナノ秒C11のスレッド系。実装がない処理系もある。
高精度待機(POSIX)nanosleeptime.h秒+ナノ秒POSIX。Windowsでは通常不可。
Windows APISleepwindows.hミリ秒Windows専用。戻り値なしで常に待つ。

標準Cだけで書くならthrd_sleep、POSIX前提ならsleepnanosleep、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環境向けの最小例です。

C言語
// ファイル名: 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)は即時復帰します。

割り込みで戻る可能性があるため、厳密に指定時間「以上」待つ必要がある場合は、nanosleepthrd_sleepで残り時間を繰り返し待機するのが堅実です。

thrd_sleepの使い方

基本の呼び出し方

thrd_sleepは、struct timespec(秒tv_sec+ナノ秒tv_nsec)で待機時間を指定します。

戻り値は0が成功、-1が割り込みなどで未完了で、その場合remainingに「残り時間」が返ります。

C言語
// ファイル名: 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_sleeptimespecをそのまま相対時間として使います。

tv_nsecは0〜999,999,999に正規化しましょう。

ミリ秒で待機

ミリ秒指定を受け取ってthrd_sleepに渡すユーティリティを作るのが実用的です。

割り込み時は残り時間でリトライします。

C言語
// ファイル名: 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へ変換して待機する例です。

C言語
// ファイル名: 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という条件分岐のラッパー関数を用意すると移植が容易です。

以下は、ミリ秒待機の移植用ラッパーの例です。

C言語
// ファイル名: 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_sleepnanosleep/Sleep」の順でフォールバックするラッパーが有効です。

割り込み時の残り時間で再試行する実装と、ミリ秒からtimespecへの正規化を身につければ、待機処理は安定して実装できます。

実際の待機時間はスケジューラの影響で伸びる可能性がある点を前提に、必要に応じて余裕を見込むと安心です。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!