如何使用C++11實現跨平台的定時器timer?
定時器要能夠在指定的時間非同步執行一個操作,比如boost庫中的boost::asio::deadline_timer,或MFC中的定時器。而且能夠被取消執行。
最基本的,能夠在x86和x64架構上的Win、Mac和Linux操作系統下實現。
一個Timer的實現需要具備以下幾個行為:
- StartTimer(Interval, ExpiryAction)
註冊一個時間間隔為 Interval 後執行 ExpiryAction 的定時器實例,其中,返回 TimerId 以區分在定時器系統中的其他定時器實例。
- StopTimer(TimerId)
根據 TimerId 找到註冊的定時器實例並執行 Stop 。
- PerTickBookkeeping()
在一個 Tick 時間粒度內,定時器系統需要執行的動作,它最主要的行為,就是檢查定時器系統中,是否有定時器實例已經到期。
具體的代碼實現思路就是:
在StartTimer的時候,把 當前時間 + Interval 作為key放入一個容器,然後在Loop的每次Tick里,從容器裡面選出一個最小的key與當前時間比較,如果key小於當前時間,則這個key代表的timer就是expired,需要執行它的ExpiryAction(一般為回調)。
這裡有兩個實現的細節:
- 獲取當前時間
這可能是實現一個定時器唯一需要關心平台差異的地方,需要注意的地方包括時間精度,使用系統開啟後的CPU時間還是壁鍾時間,常用的API是:
Windows: QueryPerformanceFrequency() 和 QueryPerformanceCounter()
Linux: clock_gettime()
OSX: gettimeofday()或者mach_absolute_time()
當然在C++11里也可以偷懶使用chrono的high_resolution_clock std::chrono::high_resolution_clock
2. Timer容器的選擇
容器應該能夠在很短的時間內找到MinValue
最小堆的find-min複雜度是O(1),所以蠻受人喜歡的
STL里提供有堆的API,make_heap, push_heap, pop_heap, sort_heap
或者直接使用std::priority_queue(std::priority_queue - cppreference.com)
3. PerTickBookkeeping是放在主循環線程還是另起線程
放在主循環的話的套路大都是基於回調函數的單線程實現,另起線程需要想好線程間通信的方式,asio和skynet有單獨的timer線程
4,一些開源項目里的timer實現
a) 這是boost.asio的實現的timer_queue,用的是最小堆
asio/timer_queue.hpp at master · chriskohlhoff/asio · GitHub
b) 這是libuv的timer,windows下採用的是紅黑樹:
libuv/timer.c at v1.x · libuv/libuv · GitHublinux下還是最小堆:
https://github.com/libuv/libuv/blob/v1.x/src/unix/timer.c
c) 這是linux kernel里的實現,經典的多級鏈表時間輪
https://github.com/torvalds/linux/blob/master/kernel/time/timer.c
時間輪的實現原理可以參考這裡: [轉載]linux 內核定時器詳解
另外,也有其它著名的開源項目採用了時間輪的方式來實現定時器,
比如,雲風的skynet skynet/skynet_timer.c at master · cloudwu/skynet · GitHub
facebook的folly裡面的HHWheelTimer https://github.com/facebook/folly/blob/master/folly/io/async/HHWheelTimer.h
參考:
Linux 下定時器的實現方式分析謝邀~MFC或者說win32 api的計時器是在windows message loop里的,可以看成main里有個for(;;){} 無限循環,掐時間觸發計時器事件。因為同時還要處理其他窗口事件,精度不高,但處於主線程中,方便調用其他窗口函數。沒用過boost的定時器,不清楚。不過可以按上面的想法實現一個跨平台的計時器。
試試libevent中的定時器?
// 實現1秒的定時器 滿足2點
1, 定時觸發(wait out time)
2, 隨時退出(notify condition)
unique_lock&
condition.wait_for(sleepLock, std::chrono::milliseconds(1000));
這裡有些例子還不錯,有最小堆和時間輪的:高性能伺服器開發之C++定時器
GitHub - pp-qq/timer: C++ 定時器, 基於 C++11, libuv 實現, 實現思路上和上面幾位差不多. 滿足問題中跨平台, 定時器可以被取消等要求.
具體實現細節就是, 在調用 `TimerManager::Start()` 之後會創建 `thread_num` 個工作線程, 每一個工作線程中都運行著一個 uv loop. 當通過 `TimerManager::StartTimer()` 提交一個定時器時, 會通過 round-robin 演算法選擇一個工作線程(下稱 P), 然後將定時器請求交給 P. P 在收到定時器請求之後會根據請求細節創建一個 `uv_timer_t` 對象來實現定時機制.
這有一個栗子struct TimerCB {
int timer_id = 0;
int times = 0;
int total_times = 5;
public:
TimerCB(int id, int total_times_arg) noexcept :
timer_id(id),
total_times(total_times_arg) {
LOG(INFO) &<&< "id: " &<&< timer_id &<&< "; times: " &<&< times &<&< "; total_times: " &<&< total_times;
}
unsigned int operator()(unsigned int status) noexcept {
LOG(INFO) &<&< "id: " &<&< timer_id &<&< "; times: " &<&< times &<&< "; total_times: " &<&< total_times
&<&< "; status: " &<&< status;
if (++times &>= total_times)
return TimerManager::kStopTimer; // 取消定時器
return 0;
}
};
int main(int argc, char **argv) {
TimerManager timer_manager;
timer_manager.thread_num = FLAGS_work_thread_num;
timer_manager.Start();
timer_manager.StartTimer(0, 0, TimerCB(1, 3));
timer_manager.StartTimer(3000, 0, TimerCB(2, 3));
timer_manager.StartTimer(0, 2000, TimerCB(3, 3));
timer_manager.StartTimer(1000, 2000, TimerCB(4, 3));
timer_manager.Join();
}
$ grep "id: 1" /tmp/test.INFO
I1007 15:53:20.716138 9764 main.cc:18] id: 1; times: 0; total_times: 3
I1007 15:53:20.716989 9765 main.cc:22] id: 1; times: 0; total_times: 3; status: 0
I1007 15:53:20.717087 9765 main.cc:22] id: 1; times: 1; total_times: 3; status: 1
$ grep "id: 2" /tmp/test.INFO
I1007 15:53:20.716536 9764 main.cc:18] id: 2; times: 0; total_times: 3
I1007 15:53:23.720160 9766 main.cc:22] id: 2; times: 0; total_times: 3; status: 0
I1007 15:53:23.720175 9766 main.cc:22] id: 2; times: 1; total_times: 3; status: 1
$ grep "id: 3" /tmp/test.INFO
I1007 15:53:20.716635 9764 main.cc:18] id: 3; times: 0; total_times: 3
I1007 15:53:20.716866 9767 main.cc:22] id: 3; times: 0; total_times: 3; status: 0
I1007 15:53:22.719032 9767 main.cc:22] id: 3; times: 1; total_times: 3; status: 0
I1007 15:53:24.721151 9767 main.cc:22] id: 3; times: 2; total_times: 3; status: 0
I1007 15:53:24.721211 9767 main.cc:22] id: 3; times: 3; total_times: 3; status: 1
$ grep "id: 4" /tmp/test.INFO
I1007 15:53:20.716729 9764 main.cc:18] id: 4; times: 0; total_times: 3
I1007 15:53:21.717957 9768 main.cc:22] id: 4; times: 0; total_times: 3; status: 0
I1007 15:53:23.720064 9768 main.cc:22] id: 4; times: 1; total_times: 3; status: 0
I1007 15:53:25.722184 9768 main.cc:22] id: 4; times: 2; total_times: 3; status: 0
I1007 15:53:25.722245 9768 main.cc:22] id: 4; times: 3; total_times: 3; status: 1
非同步執行的話另開定時器線程loop sleep就好了,使用堆或者set存儲。如果是同步的話則可以在主循環里定時檢查,Linux下則有更好用的timer fd或者alarm。
推薦閱讀:
※在 Windows 上不用 Win32 API 可以繪製出一個窗口么?
※我用的是visual studio 2010 c語言為什麼學了好長時間還是控制台程序和dos窗口啊?
※為什麼使用virtual關鍵字在C++與C#會出現不同的效果?求解答。
※使用visual studio 2012編寫每一個c程序是都必須新建工程嗎?
※我是一個物聯網新生,是先學C語言還是C++?