Windows下高精度定時器討論

獲得高精度時間點是可能的,但高精度時間段是比較難的,精度越高受到各方面的影響越大
只有QueryPerformanceCounter能突破1ms,內部就是一條彙編語句直接讀cpu晶振讀數,
但容易受到線程排隊和消息隊列延遲帶來的影響,不穩定

QueryPerformanceCounter
缺點是精度不夠高,優點是產生的間隔能突破1ms的限制,可以達到更小的間隔,理論上事件產生的頻率可以和系統定時器的頻率一樣
如果爲0.001則爲1ms產生一次,理論上如果Interval爲1,則以最大的頻率產生事件。即可以用Windows產生很高頻率的事件,但是由於線程的調用是要有時間的,有的時候可能會造成這個線程一直沒有得到執行,從而造成有一段時間沒有進行計數,這段時間的定時事件就沒有產生了,如果定時的頻率越高,丟失的可能性就越大。但如果用它來產生高頻隨時間變化的隨機信號還是很有價值的。這在實時仿真中尤其如此。

此外cpu佔用也很高,及時相應cpu就一定很忙

private void CountTime(long dwUs)
        {

            if (dwUs < 0) return;
            long ctr1 = 0, ctr2 = 0;
            if (freq == 0) QueryPerformanceFrequency(ref freq);
            if (QueryPerformanceCounter(ref ctr1) != 0)    // Begin timing.
            {
                do
                {
                    QueryPerformanceCounter(ref ctr2);    // Finish timing.
                } while (((ctr2 - ctr1) * 1.0 * 1000000 / freq) < dwUs);
            }
            else
            {
                Thread.Sleep(Convert.ToInt32(dwUs / 1000));
            }
        }
        [DllImport("kernel32.dll")]
        extern static short QueryPerformanceFrequency(ref long x);
        [DllImport("kernel32.dll")]
        extern static short QueryPerformanceCounter(ref long x);


Windows下要實現穩定的1ms定時是不可能的,Windows本來就不是實時操作系統,當初的設計就是不用來高精度定時,CreateWaitableTimer,SetWaitableTimer 可以精確到100納秒,但是波動性仍然很大.另外多媒體定時器也可以實現1ms的定時,不過最多隻能開16個定時器,而且實際時間波動也不小


我們知道, 在linux上, sleep函數的單位是s, 那怎麼進行微妙級別的定時呢? 用select函數即可。 但是, 在Windows上, 強烈不建議將select函數用作定時器(該語句出自大名鼎鼎的Windows Socket這本書), 下面我們來實戰一下:

       看程序:

  1. #include <winsock2.h>  
  2. #include <stdio.h>  
  3. #pragma comment(lib, "ws2_32.lib")  
  4.   
  5. int main()  
  6. {  
  7.     WORD wVersionRequested;  
  8.     WSADATA wsaData;  
  9.     wVersionRequested = MAKEWORD(1, 1);  
  10.     WSAStartup( wVersionRequested, &wsaData );  
  11.   
  12.     SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);  
  13.   
  14.       
  15.     fd_set read_set;  
  16.     struct timeval t;  
  17.     FD_ZERO(&read_set);   
  18.     FD_SET(sockClient, &read_set);   
  19.     t.tv_sec = 3;   
  20.     t.tv_usec = 0;  
  21.   
  22.   
  23.     int ret = select(-1, NULL, NULL, NULL, &t);  
  24.     printf("ret is %d, %d, %d\n", ret, GetLastError(), WSAEINVAL);  
  25.   
  26.   
  27.     closesocket(sockClient);  
  28.     WSACleanup();  
  29.   
  30.     return 0;  
  31. }  
       結果爲:ret is -1, 10022, 10022

       爲什麼會這樣呢? Windows Sockets 專家Bob Quinn說過, 在Windows中, 如果select函數的第2, 3, 4個參數爲NULL, 那麼, select函數會經常返回失敗的-1.


       好, 我們來改一下程序:

  1. #include <winsock2.h>  
  2. #include <stdio.h>  
  3. #pragma comment(lib, "ws2_32.lib")  
  4.   
  5. int main()  
  6. {  
  7.     WORD wVersionRequested;  
  8.     WSADATA wsaData;  
  9.     wVersionRequested = MAKEWORD(1, 1);  
  10.     WSAStartup( wVersionRequested, &wsaData );  
  11.   
  12.     SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);  
  13.   
  14.       
  15.     fd_set read_set;  
  16.     struct timeval t;  
  17.     FD_ZERO(&read_set);   
  18.     FD_SET(sockClient, &read_set);   
  19.     t.tv_sec = 3;   
  20.     t.tv_usec = 0;  
  21.   
  22.   
  23.     int ret = select(-1, &read_set, NULL, NULL, &t); // 改動了  
  24.     printf("ret is %d, %d, %d\n", ret, GetLastError(), WSAEINVAL);  
  25.   
  26.   
  27.     closesocket(sockClient);  
  28.     WSACleanup();  
  29.   
  30.     return 0;  
  31. }  
       程序的結果爲:ret is 0, 0, 10022

       而且, 我們確實看到, 起到了3秒定時的作用。 但是, 我們看看, 這個定時明顯受制於sockClient啊, 根據之前博文的分析, 如果sockClient上有數據可讀, 那麼程序會立即返回1, 從而失去了定時的作用。


       綜上所述, 在Windows上, 不要用select函數做定時器。 而在linux上, 這麼用很常見。


發佈了76 篇原創文章 · 獲贊 188 · 訪問量 184萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章