限時等待 (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_variablewait_for() / wait_until() 各有兩個重載:一個僅等待訊號/超時/虛假喚醒;另一個帶 predicate,醒來時檢查條件,僅在條件為 true(或超時)時返回。

時鐘 (Clock)

對標準庫而言,時鐘就是時間資訊源,是一個提供四種資訊的類:

  1. 當前時間:靜態成員函式 now(),如 std::chrono::system_clock::now()
  2. 時間型別:some_clock::now() 的型別即 some_clock::time_point
  3. 時鐘節拍:以 1/x 秒表示的週期,如每秒 25 拍 → std::ratio<1, 25>;每 2.5 秒一拍 → std::ratio<5, 2>
  4. 是否穩定:靜態成員 is_steady——節拍均勻分布且不可修改的時鐘稱為穩定時鐘
時鐘 特性
system_clock 系統「實際時間」(牆上時鐘);可被調整 → 不穩定;可與 time_t 互相轉換
steady_clock 穩定時鐘:is_steady == true,節拍均勻、不可調
high_resolution_clock 具有最小節拍週期(最高精度);可能是其他時鐘的 typedef

以上皆定義於 <chrono>

為何超時不能用 system_clock

std::chrono::system_clock 可被調整(例如系統校時),可能造成後一次 now() 回傳的時間早於前一次,違反節拍均勻分布。用它計算超時會隨時鐘變更而失真,所以穩定時鐘對超時計算至關重要

時間段 duration

std::chrono::duration<Rep, Period>(所有時間工具都在 std::chrono 命名空間):

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)
std::chrono::milliseconds ms(54802);
auto s = std::chrono::duration_cast<std::chrono::seconds>(ms);  // s == 54(截斷)

基於時間段的等待(等 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<>)。

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_until 而非 wait_for

條件變數迴圈是為了處理虛假喚醒;若在迴圈中用 wait_for(),每次虛假喚醒後重新呼叫,計時就重新開始——可能重複多次,造成等待時間無上限。wait_until() 以「now() + 偏移」得到的固定截止時間點為準,整體等待長度有界。

使用超時的函式一覽(表 4.1)

最簡單的用法是為執行緒加延遲:std::this_thread::sleep_for()(指定時長休眠)與 sleep_untilmutexstd::recursive_mutex 不支援超時;std::timed_mutexstd::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::timeoutcv_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

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_mutextry_lock_for/until;std::mutex 不行
「量測一段程式碼耗時」 high_resolution_clock::now() 前後相減得 duration → count()
「系統校時導致 now() 倒退」 system_clock 可調 → 不穩定;超時等待勿依賴