文章

限时等待与时钟

限时等待通过_for或_until指定等待时间,依赖chrono库的时钟与时间段实现线程超时控制。

限时等待与时钟

限时等待与时钟

阻塞调用会将线程挂起一段不确定时间,直到事件发生。一般情况下这种方式很合适,但有时需要限定等待时间,比如发送“我还存活”的心跳,或者用户可以取消等待。

超时等待通常有两种方式:

  • 时间段(duration):指定等待时长,比如等待30毫秒。
  • 时间点(time point):指定具体的绝对时间点,比如2011年11月30日 17:30:15.045987023。

对应函数名的后缀:

  • _for 表示时间段(duration)
  • _until 表示时间点(time_point)

例如,std::condition_variable 有两个超时相关成员函数:

  • wait_for()
  • wait_until()

这两个函数都有重载版本,支持直接等待、带谓词的等待等。

时钟(Clock)

C++标准库中,时钟是时间信息源,是一个类,提供以下信息:

  • 当前时间(now()静态成员函数获取)
  • 时间类型(time_point类型)
  • 时钟节拍(clock tick)
  • 是否稳定时钟(is_steady成员)

常用时钟:

  • std::chrono::system_clock 代表系统实际时间,可以调节,可能不稳定(系统时间调整可能导致时间倒退)。
  • std::chrono::steady_clock 稳定时钟,不受系统时间调整影响,保证单调递增,适合计时和超时。
  • std::chrono::high_resolution_clock 分辨率最高的时钟,通常是对上述两者之一的别名。

时间段(Duration)

时间段由std::chrono::duration<Rep, Period>表示:

  • Rep:计数类型(如int, long, double)
  • Period:单位比例,如秒的分数(std::ratio

标准预定义时间段类型:

  • nanoseconds(纳秒)
  • microseconds(微秒)
  • milliseconds(毫秒)
  • seconds(秒)
  • minutes(分钟)
  • hours(小时)

示例:

1
2
3
4
5
using namespace std::chrono_literals; // 引入 chrono 字面量后缀,如 h、min、ms

auto one_day = 24h;                  // 代表 24 小时的时间段,类型为 std::chrono::hours
auto half_an_hour = 30min;           // 代表 30 分钟的时间段,类型为 std::chrono::minutes
auto max_time_between_messages = 30ms; // 代表 30 毫秒的时间段,类型为 std::chrono::milliseconds
  • std::chrono_literals 命名空间提供的字面量后缀让你可以直接用24h30min30ms写出时间段,非常方便易读。
  • 这些变量的类型分别是std::chrono::hoursstd::chrono::minutesstd::chrono::milliseconds,都属于std::chrono::duration的特化类型。
  • 这样定义时间段可以直接用在需要时间参数的函数中,例如wait_for(half_an_hour)

时间段支持算术运算,如加减和乘除,使用count()获取原始数值。

转换时间单位需要显式调用duration_cast,因为转换会截断:

1
2
std::chrono::milliseconds ms(54802);
std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(ms); // s=54

等待future示例(基于时间段):

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <future>
#include <chrono>

// 假设some_task是一个返回int的函数或可调用对象
// std::async异步启动some_task,返回一个future对象f,用于获取异步结果
std::future<int> f = std::async(some_task);

// 等待最多35毫秒,查看异步任务是否完成
if (f.wait_for(std::chrono::milliseconds(35)) == std::future_status::ready) {
    // 如果任务在35毫秒内完成,获取结果并调用处理函数
    do_something_with(f.get());
}
// 如果超时(任务未完成),不会执行if内代码,程序继续执行后续逻辑

时间点(Time Point)

时间点由std::chrono::time_point<Clock, Duration>表示:

  • 表示某个时刻
  • time_since_epoch()返回自UNIX纪元以来的时间段

时间点可以加减时间段得到新时间点,或相减得到时间段。

示例:测量代码块执行时间

1
2
3
4
5
6
auto start = std::chrono::high_resolution_clock::now();
do_something();
auto stop = std::chrono::high_resolution_clock::now();
std::cout << "do_something() took "
          << std::chrono::duration<double>(stop - start).count()
          << " seconds\n";

使用wait_until()等待条件变量示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <condition_variable>
#include <mutex>
#include <chrono>

// 条件变量,用于线程间等待和通知
std::condition_variable cv;
// 标志变量,表示某个条件是否达成
bool done;
// 互斥量,保护对共享变量 done 的访问
std::mutex m;

// 函数:循环等待条件变量,最多等待500毫秒
bool wait_loop() {
    // 计算超时时间点:当前稳定时钟时间 + 500毫秒
    auto const timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(500);

    // 创建独占锁,锁住互斥量 m
    std::unique_lock<std::mutex> lk(m);

    // 循环等待,直到 done 为 true 或超时
    while (!done) {
        // wait_until 等待条件变量唤醒或超时
        // 参数为锁和超时的时间点
        // 返回值表示是超时还是正常唤醒
        if (cv.wait_until(lk, timeout) == std::cv_status::timeout) {
            // 超时则跳出循环,不再等待
            break;
        }
        // 如果被唤醒,重新检查 done 条件
        // 防止假唤醒导致提前返回
    }

    // 返回 done 状态,表示等待结果
    return done;
}
  • wait_until实现了最多等待500毫秒,期间如果条件变量被通知且done变为true,则提前返回。
  • 使用循环是为了防止“虚假唤醒”,即线程被意外唤醒但条件不满足时继续等待。
  • 互斥锁保证done变量的访问安全。

使用超时

超时机制可用来:

  • 让线程睡眠指定时间(sleep_for())或直到指定时间点(sleep_until())。
  • 条件变量的wait_for()wait_until()配合超时。
  • 支持超时的互斥锁类型:std::timed_mutexstd::recursive_timed_mutex,支持try_lock_for()try_lock_until()
  • std::future也支持wait_for()wait_until()
类型/命名空间函数返回值
std::this_threadsleep_for(duration)
 sleep_until(time_point)
std::condition_variablewait_for(lock, duration)std::cv_status::timeoutno_timeout
 wait_until(lock, time_point)同上
 wait_for(lock, duration, pred)bool(谓词结果)
 wait_until(lock, time_point, pred)bool(谓词结果)
std::timed_mutextry_lock_for(duration)bool(是否获取锁)
 try_lock_until(time_point)bool
std::unique_lock<TimedLockable>unique_lock(lockable, duration)构造时尝试获取锁
 unique_lock(lockable, time_point)同上
std::future<ValueType>wait_for(duration)std::future_status
 wait_until(time_point)std::future_status

总结

  • C++提供了强大的时间点和时间段类型,分别对应绝对时间和相对时间。
  • 线程同步函数支持两种超时方式,方便灵活控制等待时长。
  • 稳定时钟steady_clock是实现超时机制的首选,避免系统时间调整带来的问题。
  • 使用超时可以避免线程长时间阻塞,提高程序的响应能力和健壮性。
本文由作者按照 CC BY 4.0 进行授权