C++并发编程指南14 限时等待
C++限时等待机制解析 本文系统介绍了C++中实现线程限时等待的核心机制。主要内容包括: 限时等待的必要性:避免无限期阻塞,提升用户体验和系统健壮性,满足实时性需求。 核心计时组件: 时钟(Clock):提供时间基准,区分稳定时钟(steady_clock)和系统时钟(system_clock) 时间段(Duration):表示时间长度,支持不同精度单位和运算 时间点(Time Point):表示
文章目录
理解限时等待:别让线程无限期挂起
想象一下线程在等待某个事件(比如数据到达、锁释放、任务完成)。默认的等待(阻塞调用)会一直挂起线程,直到事件发生。这通常没问题,但有时我们需要设定一个等待期限:
-
用户交互: 用户操作界面时,长时间无响应会让人烦躁。需要超时提示或允许取消。
-
系统健壮性: 防止因某个资源永远不可用而导致整个线程(甚至程序)卡死。
-
实时性要求: 某些任务必须在特定时间内完成,超时意味着失败或需要采取替代方案。
C++ 提供了两种指定超时的方式:
-
时间段 (
_for
后缀): 等待一段相对时间(例如:再等 30 毫秒)。 -
时间点 (
_until
后缀): 等待到一个绝对时间点(例如:等到北京时间 2025-09-03 18:00:00)。
许多等待函数(如条件变量的 wait
、互斥锁的 try_lock
、future
的 wait
)都提供了这两种形式的变体。
核心概念 1:时钟 (Clocks)
在 C++ 中,时钟 (Clock)
是获取时间信息的源头。它是一个类,提供关键信息:
-
当前时间 (
now()
):std::chrono::system_clock::now()
获取系统当前时间。 -
时间点类型 (
time_point
):now()
返回的类型,例如system_clock::time_point
。 -
时钟节拍 (Tick Period): 时钟计时的最小单位(如纳秒、微秒)。定义为
std::ratio
(例如std::ratio<1, 1000>
表示毫秒)。 -
是否稳定 (
is_steady
): 稳定时钟 (is_steady == true
) 的节拍均匀且不可调整(如系统启动时间)。非稳定时钟(如系统时钟)可能被用户或 NTP 调整,导致now()
返回的时间可能比之前还早!
常用时钟:
-
std::chrono::system_clock
: 系统实时时钟(日历时间),可调整,不稳定。可转换为time_t
。 -
std::chrono::steady_clock
: 稳定时钟,最适合测量时间间隔和超时。不受系统时间调整影响。 -
std::chrono::high_resolution_clock
: 通常是系统中精度最高的时钟(最小节拍周期)。它可能是system_clock
或steady_clock
的别名。
代码示例:获取当前时间
#include <iostream>
#include <chrono>
int main() {
// 获取系统时钟的当前时间点
auto sys_now = std::chrono::system_clock::now();
// 获取稳定时钟的当前时间点
auto steady_now = std::chrono::steady_clock::now();
std::cout << "System time point: " << sys_now.time_since_epoch().count() << " ticks\n";
std::cout << "Steady time point: " << steady_now.time_since_epoch().count() << " ticks\n";
return 0;
}
核心概念 2:时间段 (Duration)
时间段表示一个时间长度。使用 std::chrono::duration
模板类表示。
-
模板参数:
-
第一个参数 (
Rep
): 存储计数值的类型 (int
,long
,double
等)。 -
第二个参数 (
Period
): 一个std::ratio
,表示每个单位是多少秒。例如std::ratio<1>
是秒,std::ratio<1, 1000>
是毫秒。
-
预定义时间段类型 (方便使用):
-
std::chrono::nanoseconds
(纳秒) -
std::chrono::microseconds
(微秒) -
std::chrono::milliseconds
(毫秒) -
std::chrono::seconds
(秒) -
std::chrono::minutes
(分钟) -
std::chrono::hours
(小时)
C++14 简化写法 (字面量后缀):
using namespace std::chrono_literals; // 启用字面量后缀
auto one_hour = 1h; // 等同于 hours(1)
auto half_min = 30min; // 等同于 minutes(30)
auto short_delay = 15ms; // 等同于 milliseconds(15)
auto precise_delay = 2.5s; // 等同于 duration<double, ratio<1,1>>(2.5)
时间段运算和转换:
-
时间段支持
+
,-
,*
,/
运算。 -
隐式转换:允许从大单位向小单位转换(例如
hours
到seconds
),只要目标类型能无损表示源值。 -
显式转换 (
duration_cast
): 需要时进行强制转换,可能截断。
#include <iostream>
#include <chrono>
int main() {
using namespace std::chrono_literals;
auto total_time = 1h + 30min + 15s + 500ms; // 组合时间段
std::cout << "Total time in seconds: "
<< std::chrono::duration_cast<std::chrono::seconds>(total_time).count()
<< " seconds\n";
std::chrono::milliseconds ms(54802); // 54802 毫秒
std::chrono::seconds sec = std::chrono::duration_cast<std::chrono::seconds>(ms); // 显式转换到秒
std::cout << "54802 ms is " << sec.count() << " seconds (truncated)\n"; // 输出 54
// 获取时间段内的计数 (单位取决于类型)
std::cout << "ms.count(): " << ms.count() << " milliseconds\n"; // 输出 54802
return 0;
}
基于时间段的等待示例 (future::wait_for
)
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
int slow_calculation() {
std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟耗时计算
return 42;
}
int main() {
std::future<int> result = std::async(std::launch::async, slow_calculation);
// 等待结果,最多等 1500 毫秒 (1.5 秒)
auto status = result.wait_for(std::chrono::milliseconds(1500));
if (status == std::future_status::ready) {
std::cout << "Result ready: " << result.get() << std::endl;
} else if (status == std::future_status::timeout) {
std::cout << "Calculation took too long! Timeout occurred." << std::endl;
} else if (status == std::future_status::deferred) {
std::cout << "Task is deferred (not started yet)." << std::endl;
}
return 0;
}
// 输出: Calculation took too long! Timeout occurred. (因为计算需要3秒,我们只等了1.5秒)
核心概念 3:时间点 (Time Point)
时间点表示时间线上的一个特定时刻。使用 std::chrono::time_point
模板类表示。
-
模板参数:
-
第一个参数 (
Clock
): 指定使用哪个时钟(如system_clock
,steady_clock
)。 -
第二个参数 (
Duration
): 指定时间点的精度(如seconds
,milliseconds
)。默认是时钟的duration
。
-
关键操作:
-
获取当前时间点:
Clock::now()
-
时间点运算: 时间点
+
时间段 = 新时间点;时间点-
时间段 = 新时间点;时间点-
时间点 = 时间段。 -
时间戳 (
time_since_epoch()
): 返回从该时钟的纪元(通常是 1970-01-01 00:00:00 UTC)到该时间点的时间长度(duration
)。
基于时间点的等待示例 (condition_variable::wait_until
)
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <atomic>
std::mutex mtx;
std::condition_variable cv;
std::atomic<bool> data_ready{false};
void data_preparation_thread() {
// 模拟准备数据需要一些时间
std::this_thread::sleep_for(std::chrono::seconds(4));
{
std::lock_guard<std::mutex> lk(mtx);
data_ready = true;
}
cv.notify_one(); // 通知等待线程
}
void data_processing_thread() {
// 设置绝对超时时间点:从现在起 2500 毫秒 (2.5 秒) 后
auto timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(2500);
std::unique_lock<std::mutex> lk(mtx);
// 使用带谓词和绝对超时的wait
if (cv.wait_until(lk, timeout, [] { return data_ready.load(); })) {
std::cout << "Data processed successfully." << std::endl;
} else {
std::cout << "Timeout: Data not ready in time. Processing aborted." << std::endl;
}
}
int main() {
std::thread t1(data_preparation_thread);
std::thread t2(data_processing_thread);
t1.join();
t2.join();
return 0;
}
// 输出: Timeout: Data not ready in time. Processing aborted.
// 因为数据准备需要4秒,但处理线程只愿意等2.5秒
重要提示:条件变量与循环
-
条件变量的等待 (
wait
,wait_for
,wait_until
) 可能因为伪唤醒(没有收到notify
就醒来)而返回。 -
因此,必须在循环中检查等待条件是否真正满足。
-
示例中的
wait_until
使用了带谓词的重载形式cv.wait_until(lk, timeout, predicate)
,它等价于:while (!predicate()) { // 条件未满足 if (cv.wait_until(lk, timeout) == std::cv_status::timeout) { return predicate(); // 超时了,最后检查一次条件 } } return true; // 条件满足了
这种形式内部已经处理了循环和伪唤醒,更简洁安全。
总结与应用场景
下表总结了 C++ 标准库中主要的支持超时的函数及其用途:
类型/命名空间 | 函数 | 返回值/行为 | 典型应用场景 |
---|---|---|---|
**std::this_thread ** |
sleep_for(duration) |
当前线程休眠指定时间段。 | 简单的延时操作。 |
sleep_until(time_point) |
当前线程休眠直到指定时间点。 | 在特定时刻唤醒线程(如定时任务)。 | |
**std::condition_variable ** |
wait_for(lock, duration) |
等待通知或超时。返回 std::cv_status::timeout 或 std::cv_status::no_timeout 。 |
等待共享数据就绪,但不愿无限期等待。 |
**(或 ..._any) ** |
wait_until(lock, time_point) |
等待通知或直到时间点。返回值同上。 | 同上,但指定绝对截止时间。 |
wait_for(lock, duration, predicate) |
等待直到谓词为 true 或超时。返回 bool (谓词结果)。 |
推荐方式:安全处理伪唤醒,等待特定条件成立。 | |
wait_until(lock, time_point, predicate) |
等待直到谓词为 true 或到时间点。返回 bool 。 |
同上,指定绝对截止时间。 | |
**std::timed_mutex ** |
try_lock_for(duration) |
尝试获取锁一段时间。成功返回 true ,否则 false 。 |
尝试获取锁,避免长时间阻塞。 |
**(或 recursive_timed_mutex) ** |
try_lock_until(time_point) |
尝试获取锁直到时间点。返回值同上。 | 同上,指定绝对截止时间。 |
**std::unique_lock<TimedLockable> ** |
unique_lock(lockable, duration) |
构造时尝试在时间段内获取锁。可通过 owns_lock() 检查是否成功。 |
构造锁时即带超时。 |
unique_lock(lockable, time_point) |
构造时尝试在时间点前获取锁。检查同上。 | 同上,指定绝对截止时间。 | |
try_lock_for(duration) |
在已关联互斥量上尝试获取锁一段时间。返回 bool 。 |
对已关联的锁尝试重新获取(较少用)。 | |
try_lock_until(time_point) |
在已关联互斥量上尝试获取锁直到时间点。返回 bool 。 |
同上。 | |
**std::future<ValueType> ** |
wait_for(duration) |
等待结果就绪、超时或发现任务延迟。返回 future_status 。 |
等待异步操作结果,设定超时。 |
**(或 shared_future<...>) ** |
wait_until(time_point) |
等待结果就绪、直到时间点或发现任务延迟。返回 future_status 。 |
同上,指定绝对截止时间。 |
选择合适的机制:
-
简单延时:
std::this_thread::sleep_for/until
-
等待异步结果:
std::future::wait_for/until
-
等待共享数据条件:
std::condition_variable::wait_for/until
(务必配合谓词循环检查!) -
尝试获取锁:
std::timed_mutex::try_lock_for/until
或std::unique_lock
的带超时构造函数 -
稳定性优先: 总是使用
std::chrono::steady_clock
来测量时间间隔和计算超时时间点!避免系统时钟调整带来的问题。
掌握这些限时等待机制,能显著提升 C++ 并发程序的响应性、健壮性和用户体验。
更多推荐
所有评论(0)