C++ <chrono> 中的steady_clock vs. system_clock

更新 發佈閱讀 14 分鐘

在OpenBMC的系統裡面,經常會遇到需要記錄log或者是需要一段時間polling一次或者一段時間要 do something。早期我對於steady_clock以及system_clock沒有太多研究,所以就覺得應該都可以吧?可是實際上兩者之間是有一些差異,沒有好壞...只是在實作的時候需要去思考哪一個比較合適。這一章聊的東西有點細節,但是我自己心目中認為能夠去在意和學習這種細節的韌體工程師,真的讓人很放心。

接下來,我們一起來了解它們的定義用途底層實作原理作業系統依賴性特性比較,並透過實例程式來說明如何正確運用這兩種時鐘。最後,我也會給出在不同情境下如何選擇合適時鐘的建議,以及常見的誤用情境和除錯的建議。

system_clock

system_clock 反映了系統當前時間,它適合用來獲取當前的日期時間戳、進行日曆時間的計算和比較,以及將時間點轉換為人類可讀的時間格式。事實上,system_clock 是 C++ 中唯一能夠將其時間點映射到 C風格時間(time_t)的時鐘類型en.cppreference.com。也就是說,我們可以使用 system_clock::to_time_t() 方法將 system_clock 的時間點轉換為日曆時間,再配合 C 的時間函數格式化輸出。下面是簡單示例:

#include <chrono>
#include <iostream>
#include <ctime>

int main() {
// 獲取當前系統時間點
auto now_tp = std::chrono::system_clock::now();
// 轉換為 time_t(日曆時間格式)
std::time_t now_c = std::chrono::system_clock::to_time_t(now_tp);
std::cout << "當前時間(UTC): " << std::ctime(&now_c);
}

上述程式中,我們使用 system_clock::now() 取得目前的時間點,接著透過 to_time_t() 轉換為 C 語言的 time_t,再使用 std::ctime 輸出可讀的時間字串。由於 system_clock 可能受系統時間調整影響,例如手動更改系統時鐘或自動夏令時間(DST)切換,因此它不保證連續單調。舉例來說,若系統時間被調慢一小時,system_clock 在調整瞬間可能會出現讀數倒退的情況(未來某一時刻返回的時間可能比過去某時刻還早),如每年秋季實施冬令時間時,時鐘會往回撥一小時,導致某一時刻(如淩晨 2:00)經歷「兩次」。總結system_clock 提供了與實際時間同步的時間點,非常適合用於需要絕對時間的場景(例如記錄日誌時間戳、設定在某個日曆時間點觸發的事件等),但由於可能被調整,不適合用於精確測量時間間隔的場景。

steady_clock

steady_clock 代表一種單調遞增的時鐘(monotonic clock),隨著物理時間推進其所提供的時間點值不會倒退,且連續兩次滴答(tick)之間的時間間隔是恆定的。steady_clock 不與任何日曆時間或時區相關,它的起點不是固定的 UTC 時間點,而往往是系統啟動後的某個時間其他任意參考點。正因為如此,steady_clock 無法直接轉換為人類可讀的日曆時間——標準庫並未提供 steady_clockto_time_t() 等函式。在 steady_clock 類別中,is_steady 靜態常量必定為 true(表示其時間值單調不減且節拍穩定)。總結來說,steady_clock 就像一支不會被人為撥動的秒錶:一直向前走,不受系統時間校準的影響。

steady_clock 保證嚴格單調且不受系統時間跳變影響,它非常適合用來計算時間間隔測量相對時間。典型的應用包括:計算程式或函式執行所花費的時間、實現 timeout(超時)計時、測量兩事件發生的時間差等等。在這些場景下,即使系統時鐘被調整(例如用戶修改了系統時間或NTP校時),steady_clock 測得的時間間隔也不會受到影響。以下是一個使用 steady_clock 測量時間間隔的簡單例子:

#include <chrono>
#include <iostream>

void some_operation() {
// 模擬一個需要耗時的操作
for (volatile int i = 0; i < 100000000; ++i) { }
}

int main() {
auto start = std::chrono::steady_clock::now();
some_operation(); // 執行需要計時的操作
auto end = std::chrono::steady_clock::now();
// 計算經過的微秒數
auto dur_us = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "操作耗時 " << dur_us.count() << " 微秒。\n";
}

在這段程式中,steady_clock::now() 返回執行開始和結束時的兩個時間點,兩者相減得到一個 duration(持續時間),我們再將其轉換為微秒並輸出。由於 steady_clock 不會因系統時間調整而跳動,整個計時過程的結果將是可靠的正值且精確反映實際執行時間。綜上,system_clocksteady_clock 分別類比於牆上時計時碼表計時:前者告訴你「現在的時間點」(可對應日曆時間),後者則告訴你「經過了多少時間」而不關心現在是幾點幾分。理解這一點將有助於我們在適當的情境中選擇正確的時鐘類型。

計算某個function執行時間這種事情通常是懷疑這個function幹了什麼不可告人的秘密,以至於想要了解他是不是花了比我想像中長的時間做了什麼不該做的事情。已經是在Debug的橋段。然而,正常我們在寫OpenBMC code的時候,不應該留到debug的時候才來看bottleneck在哪裏,應該在code裡面就已經留好等候逾時的error handling,這樣真的發生逾時的時候不需再重新複製一次問題,也可以知道是哪一段發生逾時的現象。

範例:使用 steady_clock 實現操作逾時控制

場景: 我們希望執行某個任務,但不等待超過指定時間。若超過時限則提前終止或報錯。例如,一個網路請求需要在 5 秒內完成,否則視為超時。

#include <chrono>
#include <iostream>
#include <thread>

bool do_task_with_timeout(int timeout_ms) {
using namespace std::chrono;
steady_clock::time_point deadline = steady_clock::now() + milliseconds(timeout_ms);
while (true) {
// 檢查是否已超時
if (steady_clock::now() >= deadline) {
std::cerr << "操作超時!\n";
return false;
}
// 模擬嘗試執行任務的一步
bool step_done = /* 執行任務的一步,返回是否完成 */;
if (step_done) {
std::cout << "任務在限定時間內完成。\n";
return true;
}
// 模擬短暫休息(避免忙等,不要busy wait這超重要!)
std::this_thread::sleep_for(milliseconds(100));
}
}

int main() {
do_task_with_timeout(5000); // 設定超時為5000毫秒(5秒)
}

範例:模擬固定延遲的等待

場景: 有時我們需要讓程式等待一段固定時間。例如在模擬計時器、節流某個操作或實現簡單的延時等。

#include <iostream>
#include <chrono>
#include <thread>

void delay_for(std::chrono::milliseconds delay) {
auto start = std::chrono::steady_clock::now();
auto end_time = start + delay;
// 忙等待(Busy-wait)直到抵達目標時間
while (std::chrono::steady_clock::now() < end_time) {
// 這裡可選擇讓出CPU片刻,避免完全忙等:
std::this_thread::yield();
}
}

int main() {
std::cout << "開始延遲...\n";
delay_for(std::chrono::milliseconds(2000));
std::cout << "延遲結束。\n";
}

範例:事件同步(定時任務調度)

場景: 我們希望每隔固定時間(比如每秒)執行某個動作,或者在一個指定的實時間點執行動作。例如,實現一個簡單的定時器,每隔1秒打印訊息5次。但要求即使系統時間變動,兩次打印的間隔仍接近1秒。

#include <chrono>
#include <iostream>
#include <thread>

int main() {
using namespace std::chrono;
int count = 0;
steady_clock::time_point next_time = steady_clock::now();
while (count < 5) {
// 等待直到 next_time,精確到毫秒
std::this_thread::sleep_until(next_time);
// 執行定時任務:打印訊息
std::time_t now_c = system_clock::to_time_t(system_clock::now());
std::cout << "觸發事件 " << count
<< " at time: " << std::ctime(&now_c);
++count;
// 安排下一次執行時間 = 當前 next_time + 1秒
next_time += seconds(1);
}
}

注意我們打印了 system_clock 轉成人類可讀格式的時間,以觀察真實時間。然後增計數並設定下一次觸發時間為原先的 next_time 加 1 秒。由於 steady_clock 的穩定性,即使在某次等待期間有人改了系統時鐘(比如改了系統時間或DST切換),我們依然會每隔約1秒的實際時間觸發打印一次。打印的日曆時間可能跳動,但打印之間的間隔在觀察上仍是約1秒。

結果就會像是這樣:

觸發事件 0 at time: Thu Jan  9 16:41:32 2026
觸發事件 1 at time: Thu Jan 9 16:41:33 2026
觸發事件 2 at time: Thu Jan 9 16:41:34 2026
...

突然會想寫這一篇是因為有不少同學問我system design或者有人說準備面試認體工程師要準備OS,到底都要準備些什麼?我認爲對於系統的瞭解更多一點,都是很好的準備。但這樣講未免也太抽象了!剛好看到Fan control service裡面多次用到時間的計算,就決定來寫一下這一篇,相信如果你也看了不少OpenBMC的code,你應該也對這篇的內容不陌生,因為到處都是clock....clock everywhere.....XD

好啦~今天就分享到這邊!我們下週見...謝謝請我喝珍珠奶茶的朋友們,我很愛喝,多多請我喝!我也很愛吃小籠包,跟大家分享個秘密,有一年公司叫我做一個internal project,然後我就把project name取名叫Xiao long bao「小籠包」,我說project如果成功,小籠包就可以透過程式碼躍上國際的舞台,結果沒有(沒事,我不難過 ಥ_ಥ)。如果方格子的贊助可以贊助小籠包搭配珍奶就好了鴨!哈哈哈哈哈哈~

留言
avatar-img
L'Angolo di Embedded
22會員
26內容數
這裡會有一些我對於OpenBMC, Embedded Software的學習與經驗分享, 本來只在Line社群跟大家互動, 但是有夥伴提出想要看到歷史文章的需求, 於是我決定把它放到這裡, 努力磨練自己的技術和文筆。
L'Angolo di Embedded 的其他內容
2026/01/12
探討伺服器風扇分區控制(Zone)的重要性,並更近一步介紹DbusPidZone 類別的核心職責,包括 D-Bus 通訊、感測器快取與管理、PID 迴路協調以及 Failsafe 模式。
2026/01/12
探討伺服器風扇分區控制(Zone)的重要性,並更近一步介紹DbusPidZone 類別的核心職責,包括 D-Bus 通訊、感測器快取與管理、PID 迴路協調以及 Failsafe 模式。
2026/01/05
以風扇控制為例,說明 OpenBMC 如何透過「策略與實作分離」的設計,將不同產品間的硬體差異有效收斂。從以 JSON 描述散熱拓撲與 PID 策略,到以介面抽象硬體互動,OpenBMC 避免了 hard code 與 ifdef 的擴散,展現其作為可擴充、可維護框架的核心價值。
2026/01/05
以風扇控制為例,說明 OpenBMC 如何透過「策略與實作分離」的設計,將不同產品間的硬體差異有效收斂。從以 JSON 描述散熱拓撲與 PID 策略,到以介面抽象硬體互動,OpenBMC 避免了 hard code 與 ifdef 的擴散,展現其作為可擴充、可維護框架的核心價值。
2025/12/22
本文以 BCM2711 datasheet 為核心,說明如何從硬體控制器層理解 Raspberry Pi,協助工程師將規格文件轉化為可用於系統分析與除錯的依據。
2025/12/22
本文以 BCM2711 datasheet 為核心,說明如何從硬體控制器層理解 Raspberry Pi,協助工程師將規格文件轉化為可用於系統分析與除錯的依據。
看更多
你可能也想看
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
for,while,do while語法介紹 for loop for比較偏向固定圈數型的迴圈 語法 for(計數變數初值; 布林運算式 ; 增量運算) { : 一般指令; : }
Thumbnail
for,while,do while語法介紹 for loop for比較偏向固定圈數型的迴圈 語法 for(計數變數初值; 布林運算式 ; 增量運算) { : 一般指令; : }
Thumbnail
巢狀迴圈For loop介紹結構及範例說明 巢狀迴圈 巢狀迴圈是在一個迴圈內包含另一個迴圈的結構 簡單來說,就是內迴圈做完,才會在跑到外迴圈,接著在做內迴圈
Thumbnail
巢狀迴圈For loop介紹結構及範例說明 巢狀迴圈 巢狀迴圈是在一個迴圈內包含另一個迴圈的結構 簡單來說,就是內迴圈做完,才會在跑到外迴圈,接著在做內迴圈
Thumbnail
IF,Switch,三元運算子語法說明 IF條件選擇結構說明 IF為布林條件,當()內條件式滿足True執行if區塊的程式碼,不滿足則執行else區塊的程式碼,若無else也行。
Thumbnail
IF,Switch,三元運算子語法說明 IF條件選擇結構說明 IF為布林條件,當()內條件式滿足True執行if區塊的程式碼,不滿足則執行else區塊的程式碼,若無else也行。
Thumbnail
介紹C++ 語法 資料型態,架構說明 程式語言為人類與電腦溝通的工具 程式設計流程: 定義問題 -> 問題分析 -> 撰寫演算法 ->程式撰寫 -> 程式執行及維護
Thumbnail
介紹C++ 語法 資料型態,架構說明 程式語言為人類與電腦溝通的工具 程式設計流程: 定義問題 -> 問題分析 -> 撰寫演算法 ->程式撰寫 -> 程式執行及維護
Thumbnail
break、continue、更進一步的for迴圈、while迴圈、switch
Thumbnail
break、continue、更進一步的for迴圈、while迴圈、switch
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News