#blog2navi() *VC++の sleep_for 関数の実装がよろしくない [#v5d117b5] Visual C++ 2012, 2013, 2015 の [[@code{std::this_thread::sleep_for};>cpprefjp:thread/this_thread/sleep_for]] 関数は、内部で [[@code{std::this_thread::sleep_until};>cpprefjp:thread/this_thread/sleep_until]] 関数を呼び出す実装になっています。~ 具体的に言うと、 @code{sleep_for}; の引数に渡された時間値を現在時刻に加算することで絶対時刻に変換し、それを @code{sleep_until}; 関数の引数に渡しています。((より正確に言えばVC++独自実装の @code{stdext::threads::xtime}; 型ポインタを引数に取る @code{sleep_until}; 関数なんですが、処理内容としては同じことです。)) この実装方法には問題があって、 @code{sleep_for}; 関数の処理途中に別スレッドや別プロセスによって時刻が変更されると正常に動作しません。~ 例えば現在のPC時刻が ''10時0分0秒'' で、 @code{sleep_for}; 関数の引数に ''1秒'' を渡し、その処理途中にユーザがPC時刻を ''9時0分0秒'' に変更したとすると、次のように動作します。 +@code{sleep_for}; 関数内で現在のPC時刻 ''10時0分0秒'' と引数値 ''1秒'' を加算して絶対時刻 ''10時0分1秒'' を作成。 +ここでユーザがPC時刻を ''9時0分0秒'' に変更。 +@code{sleep_until}; 関数には ''10時0分1秒'' が渡される。 +PC時刻が ''10時0分1秒'' になるまで、 ''1時間0分1秒'' の間スリープすることになってしまう。 実際にこのような挙動になることを確認するためのサンプルコードは下記の通りです。~ なお、 @code{SetSystemTime}; 関数を成功させるために管理者権限で実行する必要があります。((例えば Windows 10 ならば実行ファイルを右クリックして「管理者として実行」を選択する。)) #code(c){{ #include <thread> #include <future> #include <iostream> #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include <Windows.h> int main() { // 時刻変更スレッド実行 // 100ミリ秒おきに1年進んだり戻ったりする auto f = std::async( std::launch::async, [] { // 現在時刻取得 ::SYSTEMTIME now = { }; ::GetSystemTime(&now); // 挙動をわかりやすくするため最初に1秒待機 ::Sleep(1000); // 100ミリ秒スパンでループ for (; ; ::Sleep(100)) { // 現在のPC時刻取得 ::SYSTEMTIME t = { }; ::GetSystemTime(&t); // 本来の現在時刻より1年以上進んでいたら1年減算 // そうでなければ1年加算 if ( (t.wYear > now.wYear && t.wMonth >= now.wMonth) || (t.wYear > now.wYear + 1)) { // 1年減算してから新しい現在時刻とする --t.wYear; now = t; } else { // 新しい現在時刻としてから1年加算する now = t; ++t.wYear; } // PC時刻変更 ::SetSystemTime(&t); } }); // メインスレッドのループ for (unsigned long long i = 0; ; ++i) { // ループカウンタ表示 std::cout << i << std::endl; // sleep_for によって10ミリ秒スリープ // ★VC++だとここで1年間スリープしてしまう場合がある。 std::this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } }} コード内のコメントにも書いた通り、このサンプルでは1年間スリープすることになってしまい、プログラムが実質フリーズしてしまいます。 プログラム内では別スレッドからの時刻変更をしないように注意すればいいとしても、ユーザ操作による時刻設定を抑制するのは厳しいです。~ 当面は、素直に Windows API の @code{Sleep}; 関数等を使っておくのが無難かもしれません。 なお、gccで使われている[[libstdc++>https://gcc.gnu.org/libstdc++/]]や、clang/LLVMで使われている[[libc++>http://libcxx.llvm.org/]]における @code{sleep_for}; 関数は、引数の時間値を時刻に変換したりせずそのままシステムのスリープ関数((@code{nanosleep}; 関数等。))に渡す作りになっているため、VC++のような問題は起きません。~ @code{sleep_until}; 関数を直接呼び出した場合は同等の問題が起きる可能性がありますが、この関数は元々特定時刻まで待機するための関数なので、用途外のことに使わない限りはただ単に書いた通りに動くというだけの話でしょう。 RIGHT:Category: [[[C++>ぼやきごと/カテゴリ/C++]]][[[Visual Studio>ぼやきごと/カテゴリ/Visual Studio]]][[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]] - 2015-11-11 23:28:18 ---- RIGHT:&blog2trackback(); #comment(above) #blog2navi()