限時等待 (Waiting with Time Limits)
Overview Table
| 概念 | 重點 |
|---|---|
| 兩種超時方式 | 時間段 → _for 後綴;時間點 → _until 後綴 |
| 時鐘 (clock) | 提供 now()、time_point 型別、節拍週期 ratio、is_steady |
steady_clock |
節拍均勻且不可調整 → 計算超時的正確選擇 |
duration<Rep, Period> |
預定義 typedef、C++14 字面量後綴、duration_cast 截斷不捨入 |
time_point<Clock, Duration> |
時間戳 (epoch)、time_since_epoch()、可加減運算 → 程式計時 |
| 支援超時的函式(表 4.1) | sleep / condition_variable / timed_mutex / unique_lock / future |
兩種超時方式
阻塞呼叫會將執行緒掛起不確定的時間;需要「限定等待時間」時(定期回報「我還活著」、使用者按下取消),等待函式提供兩種超時:
| 方式 | 意義 | 後綴 | 範例 |
|---|---|---|---|
| 時間段 | 等待一段相對時長(如 30 毫秒) | _for |
wait_for(30ms) |
| 時間點 | 等到某個絕對時刻(如 UTC 17:30:15) | _until |
wait_until(deadline) |
std::condition_variable 的 wait_for() / wait_until() 各有兩個重載:一個僅等待訊號/超時/虛假喚醒;另一個帶 predicate,醒來時檢查條件,僅在條件為 true(或超時)時返回。
時鐘 (Clock)
對標準庫而言,時鐘就是時間資訊源,是一個提供四種資訊的類:
- 當前時間:靜態成員函式
now(),如std::chrono::system_clock::now() - 時間型別:
some_clock::now()的型別即some_clock::time_point - 時鐘節拍:以 1/x 秒表示的週期,如每秒 25 拍 →
std::ratio<1, 25>;每 2.5 秒一拍 →std::ratio<5, 2> - 是否穩定:靜態成員
is_steady——節拍均勻分布且不可修改的時鐘稱為穩定時鐘
| 時鐘 | 特性 |
|---|---|
system_clock |
系統「實際時間」(牆上時鐘);可被調整 → 不穩定;可與 time_t 互相轉換 |
steady_clock |
穩定時鐘:is_steady == true,節拍均勻、不可調 |
high_resolution_clock |
具有最小節拍週期(最高精度);可能是其他時鐘的 typedef |
以上皆定義於 <chrono>。
std::chrono::system_clock 可被調整(例如系統校時),可能造成後一次 now() 回傳的時間早於前一次,違反節拍均勻分布。用它計算超時會隨時鐘變更而失真,所以穩定時鐘對超時計算至關重要。
時間段 duration
std::chrono::duration<Rep, Period>(所有時間工具都在 std::chrono 命名空間):
- 第一個模板參數:數值表示型別(int、long、double)
- 第二個模板參數:
std::ratio,表示每單位是多少秒——分鐘存 short →duration<short, std::ratio<60, 1>>;毫秒存 double →duration<double, std::ratio<1, 1000>> - 預定義 typedef:
nanoseconds、microseconds、milliseconds、seconds、minutes、hours;SI 分數從std::atto(10⁻¹⁸)到std::exa(10¹⁸) - C++14
std::chrono_literals字面量後綴:
using namespace std::chrono_literals;
auto one_day = 24h;
auto half_an_hour = 30min;
auto max_time_between_messages = 30ms; // 15ns ≡ std::chrono::nanoseconds(15)
- 浮點字面量(如
2.5min)會表示為duration<some-floating-point-type, std::ratio<60,1>>;若在意精度/範圍,應自行建構物件指定型別。 - 轉換:不需截斷的轉換是隱式的(小時→秒 OK,反之不行);顯式轉換用
std::chrono::duration_cast<>,結果是截斷而非四捨五入:
std::chrono::milliseconds ms(54802);
auto s = std::chrono::duration_cast<std::chrono::seconds>(ms); // s == 54(截斷)
- 支援四則運算(
5 * seconds(1)≡seconds(5));count()取單位時間數量(milliseconds(1234).count()== 1234)。
基於時間段的等待(等 future 最多 35 毫秒):
std::future<int> f = std::async(some_task);
if milliseconds(35)) == std::future_status::ready
do_something_with(f.get());
等待函式回傳狀態值:超時 → std::future_status::timeout;就緒 → std::future_status::ready;任務被延遲(deferred)→ std::future_status::deferred。
這 35 毫秒不受系統時鐘調整影響;但系統排程的不確定性與時鐘精度,意味著實際間隔可能比 35 毫秒更長。
時間點 time_point
std::chrono::time_point<Clock, Duration>:第一參數指定時鐘、第二參數指定時間單位(特化的 duration<>)。
- 時間點的值 = 自時鐘時間戳 (epoch) 以來的時間長度;典型 epoch 為 1970-01-01 00:00(UNIX 時間戳)。
- epoch 無法直接查詢,但可呼叫
time_since_epoch()取得從 epoch 到該時間點的 duration。 - 運算:
time_point ± duration→ 新時間點(now() + nanoseconds(500)= 500 奈秒後);同時鐘的兩個 time_point 相減 → duration,可為程式碼區塊計時:
auto start = std::chrono::high_resolution_clock::now();
do_something();
auto stop = std::chrono::high_resolution_clock::now();
std::cout << "took "
<< std::chrono::duration<double>(stop - start).count()
<< " seconds\n";
帶超時的條件變數等待迴圈(代碼 4.11):
auto const timeout = std::chrono::steady_clock::now() +
std::chrono::milliseconds(500);
std::unique_lock<std::mutex> lk(m);
while (!done) {
if (cv.wait_until(lk, timeout) == std::cv_status::timeout)
break; // 超時即退出,迴圈總長有上界
}
return done;
條件變數迴圈是為了處理虛假喚醒;若在迴圈中用 wait_for(),每次虛假喚醒後重新呼叫,計時就重新開始——可能重複多次,造成等待時間無上限。wait_until() 以「now() + 偏移」得到的固定截止時間點為準,整體等待長度有界。
使用超時的函式一覽(表 4.1)
最簡單的用法是為執行緒加延遲:std::this_thread::sleep_for()(指定時長休眠)與 sleep_untilmutex 與 std::recursive_mutex 不支援超時;std::timed_mutex 與 std::recursive_timed_mutex 才有 try_lock_for() / try_lock_until(。
| 類型/命名空間 | 函式 | 回傳值 |
|---|---|---|
std::this_thread |
sleep_for(duration) / sleep_until(time_point) |
N/A |
std::condition_variable(_any) |
wait_for(lock, duration) / wait_until(lock, time_point) |
std::cv_status::timeout 或 cv_status::no_timeout |
| 同上(帶 predicate) | wait_for/wait_until(lock, ..., predicate) |
bool——醒來時 predicate 的結果 |
std::timed_mutex / std::recursive_timed_mutex |
try_lock_for(duration) / try_lock_until(time_point) |
bool——取得鎖為 true |
std::unique_lock<TimedLockable> |
建構 unique_lock(lockable, duration/time_point);try_lock_for/until |
建構後查 owns_lock();成員函式回傳 bool |
std::future<T> / std::shared_future<T> |
wait_for(duration) / wait_until(time_point) |
std::future_status::ready / timeout / deferred |
- 參數為 duration 的必須是
std::chrono::duration<>實例;time_point 的必須是std::chrono::time_point<>實例。
Exam/Test Patterns
| 情境/關鍵字 | 答案 |
|---|---|
| 「計算超時該用哪個時鐘」 | steady_clock(is_steady == true,不可調) |
「_for vs _until」 |
時間段(相對時長)vs 時間點(絕對時刻) |
「duration_cast 的結果」 |
截斷不捨入:54802ms → 54s |
「future 的 wait_for 回傳」 |
future_status:: ready / timeout / deferred |
| 「虛假喚醒導致計時重新開始」 | 迴圈內用 wait_until + 固定截止時間點,而非 wait_for |
| 「互斥鎖能限時嘗試上鎖嗎」 | std::timed_mutex / recursive_timed_mutex 的 try_lock_for/until;std::mutex 不行 |
| 「量測一段程式碼耗時」 | high_resolution_clock::now() 前後相減得 duration → count() |
「系統校時導致 now() 倒退」 |
system_clock 可調 → 不穩定;超時等待勿依賴 |
Related Notes
- 04-Synchronizing-Operations/01-Condition-Variables-and-Thread-Safe-Queue
- 04-Synchronizing-Operations/02-Futures-and-Asynchronous-Tasks
- 04-Synchronizing-Operations/04-Functional-and-Message-Passing-Styles
- 04-Synchronizing-Operations/05-Continuations-Latches-and-Barriers
- 04-Synchronizing-Operations/Practice-Synchronizing-Operations
- 03-Sharing-Data/02-Deadlock-and-Lock-Management