web-dev-qa-db-ja.com

Windowsでスレッドを1ミリ秒未満にスリープさせる方法

Windowsでは、Unixで遭遇したことがない問題があります。これが、スレッドを1ミリ秒未満スリープさせる方法です。 Unixでは、通常、ニーズに合わせていくつかの選択肢(sleep、usleep、nanosleep)があります。ただし、Windowsでは、ミリ秒単位の粒度のSleepのみがあります。

Unixでは、selectシステムコールを使用して、非常に簡単なマイクロ秒のスリープを作成できます。

int usleep(long usec)
{
    struct timeval tv;
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, 0, &tv);
}

Windowsで同じことをどのように達成できますか?

51
Jorge Ferreira

これは、睡眠機能の誤解を示しています。渡すパラメーターは、最小スリープ時間です。指定された正確な時間後にスレッドが起動するという保証はありません。実際、スレッドはまったく「起動」せず、スケジューラによる実行用に選択されます。特にその時点で別のスレッドがまだアクティブである場合、スケジューラは、要求されたスリープ期間よりもはるかに長く待機してスレッドをアクティブにすることを選択する場合があります。

89
Joel Coehoorn

ジョエルが言うように、あなたはそのような短期間の間、有意に「眠る」(すなわち、スケジュールされたCPUを手放す)ことはできません。少しの間遅延させたい場合は、スピンして、適切な高解像度のタイマー(「パフォーマンスタイマー」など)を繰り返しチェックし、優先度の高いものが先を占領しないことを期待する必要があります。

このような短い時間の正確な遅延を本当に気にする場合は、Windowsを使用しないでください。

46
Will Dean

Winmm.libで利用可能な高解像度タイマーを使用します。例については、 this を参照してください。

29
Joe Schneider
#include <Windows.h>

static NTSTATUS(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");

static NTSTATUS(__stdcall *ZwSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution) = (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");




static void SleepShort(float milliseconds) {
    static bool once = true;
    if (once) {
        ULONG actualResolution;
        ZwSetTimerResolution(1, true, &actualResolution);
        once = false;
    }

    LARGE_INTEGER interval;
    interval.QuadPart = -1 * (int)(milliseconds * 10000.0f);
    NtDelayExecution(false, &interval);
}

はい、文書化されていないカーネル関数を使用しますが、非常にうまく機能します。SleepShort(0.5)を使用します。私のツレの一部で

11
Oskar Dahlberg

はい、OSのタイムクォンタムを理解する必要があります。 Windowsでは、タイムクォンタムを1ミリ秒に変更しない限り、1ミリ秒の解像度を取得することさえできません。 (たとえば、timeBeginPeriod()/ timeEndPeriod()を使用する)それでも、実際には何も保証されません。少しの負荷または単一の安っぽいデバイスドライバーでさえ、すべてを捨てます。

SetThreadPriority()は役立ちますが、非常に危険です。不良なデバイスドライバーは、依然としてユーザーを台無しにする可能性があります。

このいものを機能させるには、非常に制御されたコンピューティング環境が必要です。

10
darron

細分性が必要な場合は、間違った場所(ユーザー空間)にいます。

ユーザー空間にいる場合、時間は必ずしも正確ではないことに注意してください。

スケジューラーはスレッド(またはアプリ)を開始し、それをスケジュールできるため、OSスケジューラーに依存します。

正確なものを探している場合は、1)カーネルスペース(ドライバーなど)で2)RTOSを選択します。

とにかく、ある程度の粒度を探している場合(ただし、ユーザースペースの問題を覚えている場合)、MSDNのQueryPerformanceCounter関数とQueryPerformanceFrequency関数を参照してください。

6
user16523

通常、スリープは少なくとも次のシステム割り込みが発生するまで続きます。ただし、これはマルチメディアタイマーリソースの設定に依存します。 1ミリ秒に近い値に設定される場合があり、ハードウェアによってはNtQueryTimerResolutionによって提供される0.9765625(ActualResolutionの割り込み周期で実行することもできます。 0.9766と表示されますが、実際は間違っています。正しい数値をActualResolution形式に入れることはできません。1秒あたり1024割り込みで0.9765625msです)。

割り込み期間よりも短い時間スリープできないという事実から逃れることができる例外が1つあります。これは有名なSleep(0)です。これは非常に強力なツールであり、必要な頻度では使用されません!スレッドのタイムスライスのリマインダーを放棄します。この方法では、スケジューラーがスレッドにCPUサービスを再度取得させるまで、スレッドは停止します。 Sleep(0)は非同期サービスです。この呼び出しにより、スケジューラーは割り込みに依存せずに反応します。

2番目の方法は、_waitable object_の使用です。 WaitForSingleObject()などの待機関数は、イベントを待機できます。スレッドを任意の時間(マイクロ秒体制でも)スリープさせるには、スレッドは、必要な遅延でイベントを生成するサービススレッドをセットアップする必要があります。 「スリープ」スレッドはこのスレッドをセットアップし、サービススレッドがイベントをシグナルするまで待機関数で一時停止します。

このように、どのスレッドもいつでも「スリープ」または待機できます。サービススレッドは非常に複雑になる可能性があり、マイクロ秒の解像度で時限イベントのようなシステム全体のサービスを提供する場合があります。ただし、マイクロ秒の解像度では、サービススレッドが最大1つの割り込み期間(〜1ms)の間、高解像度のタイムサービスで強制的にスピンする場合があります。注意を払えば、特にマルチプロセッサまたはマルチコアシステムで非常にうまく動作します。マルチコアシステムでは、呼び出しスレッドとサービススレッドのアフィニティマスクが慎重に処理されるため、1ミリ秒のスピンはそれほど害になりません。

コード、説明、およびテストは、 Windows Timestamp Project でご覧いただけます

5
Arno

数人が指摘しているように、睡眠と他の関連機能はデフォルトで「システムティック」に依存しています。これは、OSタスク間の最小時間単位です。たとえば、スケジューラはこれより速く実行されません。リアルタイムOSであっても、システムティックは通常1ミリ秒未満ではありません。これは調整可能ですが、スケジューラがより頻繁に実行され、OSのオーバーヘッド(スケジューラの実行時間対vs.タスクを実行できる時間)。

これに対する解決策は、外部の高速クロックデバイスを使用することです。ほとんどのUnixシステムでは、デフォルトのシステムクロックではなく、使用するタイマーとそのような異なるクロックを指定できます。

5
mbyrne215

そのような精度が必要なのを待っていますか?一般的にneedでその精度レベルを指定する場合(外部ハードウェアへの依存など)、間違ったプラットフォームにいるため、リアルタイムOSを確認する必要があります。

それ以外の場合は、同期できるイベントがあるかどうかを検討する必要があります。最悪の場合、CPUをビジー状態で待機し、高性能カウンターAPIを使用して経過時間を測定します。

4
Rob Walker

SetWaitableTimer ...を使用してみてください.

2
andrewrk

このusleep関数を実際に使用すると、大きなメモリ/リソースリークが発生します。 (呼び出される頻度に依存)

この修正版を使用してください(編集できませんか?)

bool usleep(unsigned long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec / 1000000ul;
    tv.tv_usec = usec % 1000000ul;
    bool success = (0 == select(0, 0, 0, &dummy, &tv));
    closesocket(s);
    return success;
}
2
Hendrik

私は同じ問題を抱えており、Sleep(0)であっても、msより速いものはないようです。私の問題は、クライアントとサーバーアプリケーション間の通信であり、_InterlockedExchange関数を使用してビットをテストおよび設定してから、Sleep(0)にします。

毎秒何千もの操作をこの方法で実行する必要があり、それは計画したほど速く動作しません。

ユーザーを処理するシンクライアントを持っているため、エージェントが呼び出されてエージェントがスレッドと通信するため、イベントインターフェイスが不要になるように、すぐにスレッドをエージェントにマージします。

このスリープがどれほど遅いかをおわかりいただけるように、空のループ(18,000,000ループなど)を実行して10秒間テストを実行しましたが、イベントの場所では180,000ループしかありませんでした。つまり、100倍遅くなります!

2
Celso Bressan

誰もが言及したように、実際に睡眠時間についての保証はありません。しかし、アイドル状態のシステムでは、時々usleepコマンドが非常に正確になる可能性があることを認めたくありません。特にティックレスカーネルでは。 Windows Vistaにはそれがあり、Linuxには2.6.16以降があります。

ティックレスカーネルは、ラップトップのバッテリー寿命を向上させるのに役立ちます:c.f. Intelのpowertopユーティリティ。

その状態で、私はたまたま要求されたスリープ時間を尊重するLinux usleepコマンドを測定しました。

そのため、OPはアイドリングシステムでほとんどの時間をおおまかに動作し、マイクロ秒のスケジューリングを要求できるものを望んでいる可能性があります。私は実際にWindowsでもそれが欲しいでしょう。

また、Sleep(0)はboost :: thread :: yield()のように聞こえますが、この用語はより明確です。

Boost -timedロックの方が精度が高いのだろうか。そのため、誰もリリースしていないミューテックスをロックするだけで、タイムアウトに達したら続行します...タイムアウトはboost :: system_time + boost :: milliseconds&cieで設定されます(xtimeは非推奨です)。

1
Lightness1024

Boost :: xtimeとtimed_wait()を試してください

ナノ秒の精度があります。

0
theschmitzer

Sleep(0)を使用してください。 0は明らかに1ミリ秒未満です。今、それは面白そうに聞こえますが、私は真剣です。 Sleep(0)は、今は何もする必要はないが、スケジューラが再び実行されるとすぐに再検討したいことをWindowsに伝えます。また、スケジューラ自体が実行される前にスレッドを実行するようにスケジュールできないことは明らかなので、これは可能な限り最短の遅延です。

マイクロ秒数をusleepに渡すことができますが、void usleep(__ int64 t){Sleep(t/1000); }-その期間を実際にスリープする保証はありません。

0
MSalters

目標が"非常に短い時間待機する"である場合、spinwaitを実行しているため、実行できる待機レベルが増加しています。

void SpinOnce(ref Int32 spin)
{
   /*
      SpinOnce is called each time we need to wait. 
      But the action it takes depends on how many times we've been spinning:

      1..12 spins: spin 2..4096 cycles
      12..32: call SwitchToThread (allow another thread ready to go on time core to execute)
      over 32 spins: Sleep(0) (give up the remainder of our timeslice to any other thread ready to run, also allows APC and I/O callbacks)
   */
   spin += 1;

   if (spin > 32)
      Sleep(0); //give up the remainder of our timeslice
   else if (spin > 12)
      SwitchTothread(); //allow another thread on our CPU to have the remainder of our timeslice
   else
   {
      int loops = (1 << spin); //1..12 ==> 2..4096
      while (loops > 0)
         loops -= 1;
   }
}

したがって、目標が実際に待機することである場合少しだけ、次のようなものを使用できます:

int spin = 0;
while (!TryAcquireLock()) 
{ 
   SpinOne(ref spin);
}

ここでの長所は、毎回長く待機し、最終的に完全にスリープ状態になることです。

0
Ian Boyd

1ミリ秒未満のスリープ関数

Sleep(0)が私のために働いていることがわかりました。タスクマネージャーのCPUの負荷が0%に近いシステムで、単純なコンソールプログラムを作成し、sleep(0)関数を一貫して1〜3マイクロ秒(1ミリ秒未満)スリープさせました。

しかし、このスレッドの上記の回答から、sleep(0)のスリープ量は、CPU負荷が大きいシステムではこれよりも大幅に変化する可能性があることがわかります。

しかし、私が理解しているように、スリープ機能はタイマーとして使用すべきではありません。これは、プログラムでCPUの使用率をできるだけ低くし、できるだけ頻繁に実行するために使用する必要があります。私の目的、たとえばビデオゲームのスクリーン上で発射物を1ミリ秒あたり1ピクセルよりもはるかに速く動かすなど、sleep(0)は機能すると思います。

スリープ間隔は、スリープする最長時間よりもはるかに短くするだけです。スリープをタイマーとして使用するのではなく、ゲームで可能な限り最小のCPUパーセンテージを使用するようにします。特定の時間が経過したときを知るためにスリープすることは何もしない別の関数を使用し、その後、1/10ミリ秒または100マイクロ秒の時間で、スクリーン上で発射体を1ピクセル移動します。

擬似コードは次のようになります。

while (timer1 < 100 microseconds) {
sleep(0);
}

if (timer2 >=100 microseconds) {
move projectile one pixel
}

//Rest of code in iteration here

答えは高度な問題やプログラムでは機能しないかもしれませんが、一部または多くのプログラムでは機能するかもしれません。

0
rauprog