簡體   English   中英

如何使 Windows 上的線程睡眠時間少於一毫秒

[英]How to make thread sleep less than a millisecond on Windows

在 Windows 上,我遇到了一個在 Unix 上從未遇到過的問題。那就是如何讓一個線程休眠不到一毫秒。 在 Unix 上,您通常有多種選擇(睡眠、睡眠和納米睡眠)來滿足您的需求。 而在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 上達到同樣的效果?

這表明對睡眠功能的誤解。 您傳遞的參數是睡眠的最短時間。 不能保證線程會在指定的時間后喚醒。 事實上,線程根本不會“喚醒”,而是由 OS 調度程序選擇執行。 調度程序可能會選擇等待比請求的睡眠持續時間更長的時間來激活線程,尤其是當另一個線程此時仍處於活動狀態時。

正如 Joel 所說,您不能在如此短的時間內有意義地“睡眠”(即放棄您預定的 CPU)。 如果您想延遲一小段時間,那么您需要旋轉,反復檢查適當的高分辨率計時器(例如“性能計時器”),並希望高優先級的東西無論如何都不會搶占您。

如果您真的關心如此短時間的准確延遲,那么您不應該使用 Windows。

使用 winmm.lib 中提供的高分辨率多媒體計時器。 請參閱示例。

#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);
}

非常適合在極短的時間內睡覺。 請記住,在某一點上,實際延遲永遠不會是一致的,因為系統無法在如此短的時間內保持一致的延遲。

是的,您需要了解操作系統的時間量。 在 Windows 上,除非您將時間片更改為 1 毫秒,否則您甚至不會獲得 1 毫秒的分辨率時間。 (例如使用 timeBeginPeriod()/timeEndPeriod())這仍然不能保證任何事情。 即使是一點點負載或一個蹩腳的設備驅動程序也會讓一切崩潰。

SetThreadPriority() 有幫助,但非常危險。 糟糕的設備驅動程序仍然會毀了你。

你需要一個超可控的計算環境才能讓這些丑陋的東西完全發揮作用。

通常睡眠至少會持續到下一個系統中斷發生。 但是,這取決於多媒體定時器資源的設置。 它可能設置為接近 1 ms,某些硬件甚至允許以 0.9765625 的中斷周期運行(由NtQueryTimerResolution提供的NtQueryTimerResolution將顯示 0.9766 但這實際上是錯誤的。他們只是無法將正確的數字放入ActualResolution格式中。這是每秒 1024 次中斷時為 0.9765625 毫秒)。

有一個例外可以讓我們擺脫這樣一個事實,即不可能在中斷周期內睡眠:它是著名的Sleep(0) 這是一個非常強大的工具,它沒有像應有的那樣經常使用! 它放棄了線程時間片的提醒。 這樣線程將停止,直到調度程序強制線程再次獲得 cpu 服務。 Sleep(0)是一個異步服務,調用將強制調度程序獨立於中斷作出反應。

第二種方法是使用可waitable object WaitForSingleObject()這樣的等待函數可以等待一個事件。 為了讓線程在任何時候都處於休眠狀態,也是微秒級的時間,線程需要設置一些服務線程,該線程將在所需的延遲時生成事件。 “睡眠”線程將設置此線程,然后在等待函數處暫停,直到服務線程將設置事件信號。

這樣任何線程都可以“休眠”或等待任何時間。 服務線程可能非常復雜,它可以提供系統范圍的服務,例如微秒級的定時事件。 但是,微秒分辨率可能會迫使服務線程在一個高分辨率時間服務上旋轉最多一個中斷周期(~1ms)。 如果小心,它可以很好地運行,特別是在多處理器或多核系統上。 當調用線程和服務線程的關聯掩碼得到仔細處理時,一毫秒的自旋在多核系統上不會造成太大的傷害。

可以在Windows 時間戳項目中訪問代碼、描述和測試

正如一些人所指出的,睡眠和其他相關功能默認依賴於“系統滴答”。 這是操作系統任務之間的最小時間單位; 例如,調度程序不會運行得比這更快。 即使使用實時操作系統,系統滴答時間通常也不小於 1 毫秒。 雖然它是可調的,但這會對整個系統產生影響,而不僅僅是您的睡眠功能,因為您的調度程序將更頻繁地運行,並且可能會增加您的操作系統的開銷(調度程序運行的時間量與任務可以運行的時間)。

解決方案是使用外部高速時鍾設備。 大多數 Unix 系統允許你指定你的計時器和這樣一個不同的時鍾來使用,而不是默認的系統時鍾。

您還在等什么,需要如此精確? 一般來說,如果您需要指定該級別的精度(例如,由於對某些外部硬件的依賴),您就在錯誤的平台上,應該查看實時操作系統。

否則,您應該考慮是否有可以同步的事件,或者在更糟糕的情況下只是忙於等待 CPU 並使用高性能計數器 API 來測量經過的時間。

如果您想要如此多的粒度,那么您來錯地方了(在用戶空間中)。

請記住,如果您在用戶空間中,您的時間並不總是准確的。

調度程序可以啟動您的線程(或應用程序)並對其進行調度,因此您依賴於操作系統調度程序。

如果您正在尋找精確的東西,您必須去:1)在內核空間(如驅動程序)中 2)選擇一個 RTOS。

無論如何,如果您正在尋找一些粒度(但請記住用戶空間的問題),請查看 MSDN 中的 QueryPerformanceCounter 函數和 QueryPerformanceFrequency 函數。

實際上使用這個 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;
}

嘗試使用SetWaitableTimer ...

我有同樣的問題,似乎沒有什么比一毫秒更快,甚至是睡眠(0)。 我的問題是客戶端和服務器應用程序之間的通信,我使用 _InterlockedExchange 函數來測試和設置一點,然后我休眠(0)。

我確實需要以這種方式每秒執行數千次操作,但它的工作速度不如我計划的那么快。

由於我有一個與用戶打交道的瘦客戶端,它反過來調用一個代理,然后與一個線程對話,我將很快將線程與代理合並,這樣就不需要事件接口。

只是為了讓你們知道這個 Sleep 有多慢,我運行了一個 10 秒的測試,執行一個空循環(得到大約 18,000,000 個循環),而在事件發生后我只有 180,000 個循環。 也就是說,慢了 100 倍!

就像大家提到的那樣,睡眠時間確實無法保證。 但沒有人願意承認,有時在空閑系統上,usleep 命令可能非常精確。 特別是使用無滴答內核。 Windows Vista 擁有它,Linux 從 2.6.16 開始擁有它。

Tickless 內核的存在有助於提高筆記本電腦的電池壽命:參見英特爾的 powertop 實用程序。

在那種情況下,我碰巧測量了 Linux usleep 命令,它非常接近請求的睡眠時間,低至半打微秒。

所以,也許 OP 想要一些在空閑系統上大部分時間都能大致工作的東西,並且能夠要求微秒調度! 我實際上也希望在 Windows 上使用它。

另外,Sleep(0) 聽起來像 boost::thread::yield(),術語更清楚。

我想知道Boost定時鎖是否有更好的精度。 因為那時你可以鎖定一個沒有人釋放的互斥鎖,當達到超時時,繼續...超時設置為 boost::system_time + boost::milliseconds & cie(不推薦使用 xtime)。

如果您的目標是“等待很短的時間” ,因為您正在執行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);
}

這里的優點是我們每次都等待更長的時間,最終完全進入睡眠狀態。

不到一毫秒的睡眠功能-也許

我發現 sleep(0) 對我有用。 在任務管理器中 cpu 負載接近 0% 的系統上,我編寫了一個簡單的控制台程序,並且 sleep(0) 函數的睡眠時間一致為 1-3 微秒,不到一毫秒。

但是從這個線程的上述答案中,我知道 sleep(0) 睡眠量在 CPU 負載較大的系統上的變化可能比這大得多。

但據我了解,睡眠功能不應該用作計時器。 它應該用於使程序使用盡可能少的 cpu 百分比並盡可能頻繁地執行。 就我的目的而言,例如在視頻游戲中以比每毫秒一個像素快得多的速度在屏幕上移動彈丸,我認為 sleep(0) 有效。

您只需確保睡眠間隔小於最大睡眠時間。 您不將睡眠用作計時器,而只是為了使游戲使用盡可能少的 cpu 百分比。 您將使用一個與睡眠無關的單獨功能,以了解何時過去了特定的時間,然后在屏幕上移動一個像素——例如 1/10 毫秒或 100 微秒.

偽代碼會是這樣的。

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

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

//Rest of code in iteration here

我知道答案可能不適用於高級問題或程序,但可能適用於某些或許多程序。

如果計算機運行的是 Windows 10 版本 1803 或更高版本,則可以使用帶有CREATE_WAITABLE_TIMER_HIGH_RESOLUTION標志的CreateWaitableTimerExW

嘗試 boost::xtime 和 timed_wait()

具有納秒精度。

只需使用睡眠(0)。 0 顯然小於一毫秒。 現在,這聽起來很有趣,但我是認真的。 Sleep(0) 告訴 Windows 您現在沒有任何事情可做,但您確實希望在調度程序再次運行時重新考慮。 而且由於顯然無法在調度程序本身運行之前調度線程運行,因此這是可能的最短延遲。

請注意,您可以將微秒數傳遞給您的睡眠,但 void usleep(__int64 t) { Sleep(t/1000); } - 不能保證那段時間真的睡着了。

在 Windows 上,使用select會強制您在應用程序中包含必須像這樣初始化的Winsock庫:

WORD wVersionRequested = MAKEWORD(1,0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);

然后 select 不允許你在沒有任何套接字的情況下被調用,所以你必須做更多的事情來創建一個 microsleep 方法:

int usleep(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/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, &dummy, &tv);
}

所有這些創建的 usleep 方法在成功時返回零,在錯誤時返回非零。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM