HOWTO · C++

C++ に時限遅延を追加

このチュートリアルでは、C++ プログラムに時限遅延を追加するための簡単なガイドを提供します。

このページの内容

C++プログラムでは、プラットフォームAPIまたは標準ライブラリのスレッド待機機能を使って、現在のスレッドを一時停止できます。移植性と、待ちたい対象が経過時間か時刻かに応じて選びます。POSIXシステムの sleep() は秒単位、usleep() はマイクロ秒単位で、C++11の sleep_for() と sleep_until() は期間と時刻を受け取ります。いずれも呼び出したスレッドを停止しますが、正確な時刻に再開する保証はありません。

C++の遅延APIを選ぶ

一般的なアプリケーションコードで移植性のあるC++11の相対待機を行うには、sleep_for() を使います。時計上の期限まで処理を待たせる場合は sleep_until() を使います。POSIXの sleep() と usleep() は、すでにPOSIXに依存するコードでは便利ですが、移植可能なC++機能ではありません。Windowsには独自の Sleep() APIがあり、引数の単位はミリ秒です。

遅延処理は、呼び出したスレッドをブロックします。ユーザーインターフェイス、サーバー、ワーカープールの応答性を保つ必要がある場合は、メインスレッドやイベントループをブロックせず、専用スレッドを一時停止するか、フレームワークの非同期タイマーを使ってください。

sleep() で秒単位のプラットフォーム固有の遅延を行う

C++には、移植可能な sleep() 関数は定義されていません。POSIXシステムでは <unistd.h> で宣言し、sleep(seconds) を呼び出します。シグナルによって待機が中断された場合は、要求した秒数のうち残っている秒数を返し、指定した時間が経過すると0を返します。Windowsでは <Windows.h> をインクルードして Sleep(milliseconds) を呼び出します。単位はミリ秒なので、1秒待つには Sleep(1000) とします。POSIX版とWindows版では、関数名、ヘッダー、単位が異なります。Windows用ヘッダーについてはWindows.hのガイドも参照してください。

たとえばPOSIXでは、<unistd.h> をインクルードしたうえで sleep(2); を呼び出します。Windowsでは、<Windows.h> をインクルードして Sleep(2000); を使います。対象プラットフォームに応じて分岐を選んでください。POSIXの宣言を追加しても、Windowsで sleep() が使えるようにはなりません。

usleep() でPOSIXのマイクロ秒単位の遅延を行う

POSIXシステムでは、usleep() は <unistd.h> で宣言され、マイクロ秒単位の期間を受け取ります。たとえば usleep(250000); は、4分の1秒の待機を要求します。これは移植可能なC++ではなくPOSIXのインターフェイスです。プラットフォームや機能設定によっては利用できない場合があるため、複数の環境で動くC++コードには sleep_for() を使ってください。待機が完了すると 0 を返し、中断された場合やその他の理由で失敗した場合は -1 を返します。中断時に呼び出し側で対応する必要がある場合は、戻り値を確認してください。OSによるスレッドのスケジューリングのため、実際の経過時間は要求した時間を超えることがあります。

sleep_for() で相対的な時間だけ待つ

相対的な期間だけ待つには、<thread> と <chrono> をインクルードします。sleep_for() は、スケジューリングやリソース競合の影響を受けながら、指定した期間以上、現在のスレッドを停止します。この完全なC++11プログラムは200ミリ秒待ってから確認メッセージを表示します。次の例を実行してください。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <chrono>
#include <iostream>
#include <thread>

int main() {
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    std::cout << "Wait complete\n";
}
Wait complete

意図した単位が明確になる期間型を使いましょう。たとえば std::chrono::seconds(2) や std::chrono::milliseconds(250) のように指定できます。待機の影響を受けるのは呼び出したスレッドだけです。スケジューラの遅延により、実際の経過時間が関数に渡した期間より長くなることがあります。

sleep_until() で時刻まで待つ

sleep_until() も <thread> の機能ですが、期間ではなく時計上の時刻を受け取ります。時計の now() が返す時刻に期間を加えると、将来の期限を作れます。たとえば std::this_thread::sleep_until(std::chrono::steady_clock::now() + std::chrono::seconds(3)); のように書きます。これは、壁時計のように調整されず、経過時間の計測を目的とする steady_clock で、その時刻まで待機します。期限がすでに過去なら、追加の待機は発生しません。この関数は値を返さず、スケジューリングの都合で期限を過ぎてからスレッドが再開することがあります。

経過時間を基準に期限を設定する場合は、システム時計の補正で目標時刻が動かないよう steady_clock を使うのが適切です。壁時計上の時刻が目標の場合に限り system_clock を使い、待機中に時計が変更される可能性を考慮してください。sleep_for() と同様、呼び出したスレッドはブロックされます。

適切な遅延方法を選び、スケジューリングを考慮する

移植可能なC++11コードでは、「この長さだけ待つ」場合に sleep_for() を、「この期限まで待つ」場合に sleep_until() を使います。POSIX固有の動作が適切な場合に限り sleep() または usleep() を使い、Windows固有のコードではWindowsの Sleep() を使います。単位が分かるようにしておきましょう。sleep() の単位は秒、usleep() はマイクロ秒、Windowsの Sleep() はミリ秒です。<chrono> では名前付きの型で期間を表します。

これらのAPIはいずれも高精度タイマーではありません。指定時間は最低限の待機目安であり、正確な再開時刻を保証するものではありません。スケジューリング、競合、割り込みの影響で、処理が再開する時刻は変わります。スレッドをブロックできない短い待機には、プラットフォームやフレームワークが提供する非同期タイマーを使ってください。こうした違いを踏まえることで、プログラムの移植性と時間の扱い方に合った遅延方法を選べます。