限时等待与时钟
限时等待通过_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
命名空间提供的字面量后缀让你可以直接用24h
、30min
、30ms
写出时间段,非常方便易读。- 这些变量的类型分别是
std::chrono::hours
、std::chrono::minutes
和std::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_mutex
和std::recursive_timed_mutex
,支持try_lock_for()
和try_lock_until()
。 std::future
也支持wait_for()
和wait_until()
。
类型/命名空间 | 函数 | 返回值 |
---|---|---|
std::this_thread | sleep_for(duration) | 无 |
sleep_until(time_point) | 无 | |
std::condition_variable | wait_for(lock, duration) | std::cv_status::timeout 或no_timeout |
wait_until(lock, time_point) | 同上 | |
wait_for(lock, duration, pred) | bool (谓词结果) | |
wait_until(lock, time_point, pred) | bool (谓词结果) | |
std::timed_mutex | try_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 进行授权