C++并发编程指南 10 死锁预防 (超时锁)
摘要 超时锁是多线程编程中防止线程无限等待的机制,允许设置获取锁的时间限制。文章详细介绍了超时锁的核心概念,包括避免死锁、提高响应性等优势,并演示了C++中的两种实现方式:try_lock_for()(相对时间)和try_lock_until()(绝对时间)。通过三个完整代码示例展示了基础超时锁定、高级多锁协同获取以及多线程环境下的超时锁应用,每种情况都包含详细的超时处理和资源释放逻辑。这些实现均
文章目录
超时锁(Timeout Locks)详解与完整演示
超时锁是一种在多线程编程中避免无限期等待的机制,它允许线程在尝试获取锁时设置一个时间限制。如果在该时间内无法获取锁,线程可以选择放弃并执行其他操作,而不是无限期阻塞。
超时锁的核心概念
1. 为什么需要超时锁
-
避免死锁:当多个线程以不同顺序获取锁时可能导致死锁
-
提高响应性:在实时系统中,不能无限期等待资源
-
资源争用管理:当资源繁忙时,可以执行备选方案
-
系统稳定性:防止线程永久阻塞导致系统挂起
2. C++中的超时锁机制
C++标准库提供了两种超时锁定方式:
-
try_lock_for()
:在指定的相对时间内尝试获取锁 -
try_lock_until()
:在指定的绝对时间点前尝试获取锁
这些方法可用于std::unique_lock
和std::shared_lock
等锁类型。
完整超时锁演示代码
#include <iostream>
#include <mutex>
#include <thread>
#include <chrono>
#include <vector>
#include <random>
#include <iomanip>
#include <string>
// 安全输出函数
void safe_print(const std::string& message) {
static std::mutex print_mutex;
std::lock_guard<std::mutex> lock(print_mutex);
std::cout << message << std::endl;
}
// 使用支持超时的互斥量
std::timed_mutex mutex1;
std::timed_mutex mutex2;
// 1. 基本超时锁定演示
void basic_timeout_demo() {
safe_print("===== 基本超时锁定演示 =====");
// 使用支持超时的unique_lock
std::unique_lock<std::timed_mutex> lock1(mutex1, std::defer_lock);
std::unique_lock<std::timed_mutex> lock2(mutex2, std::defer_lock);
auto start = std::chrono::steady_clock::now();
// 尝试在100ms内获取第一个锁
if (lock1.try_lock_for(std::chrono::milliseconds(100))) {
safe_print("成功获取mutex1");
// 尝试在100ms内获取第二个锁
if (lock2.try_lock_for(std::chrono::milliseconds(100))) {
safe_print("成功获取mutex2");
safe_print("执行关键操作...");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
else {
safe_print("获取mutex2超时");
}
}
else {
safe_print("获取mutex1超时");
}
// RAII自动释放锁
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
safe_print("操作耗时: " + std::to_string(duration.count()) + "ms");
}
// 2. 高级超时锁定:同时尝试多个锁
void advanced_timeout_demo() {
safe_print("\n===== 高级超时锁定演示 =====");
std::unique_lock<std::timed_mutex> lock1(mutex1, std::defer_lock);
std::unique_lock<std::timed_mutex> lock2(mutex2, std::defer_lock);
auto start = std::chrono::steady_clock::now();
auto timeout_point = start + std::chrono::milliseconds(150);
// 尝试同时锁定两个互斥量
if (std::try_lock(lock1, lock2) == -1) {
safe_print("立即成功获取两个锁");
}
else {
safe_print("未能立即获取两个锁,尝试超时锁定");
// 尝试在剩余时间内获取锁
while (std::chrono::steady_clock::now() < timeout_point) {
if (lock1.try_lock()) {
safe_print("成功获取mutex1");
if (lock2.try_lock_until(timeout_point)) {
safe_print("成功获取mutex2");
break;
}
else {
safe_print("获取mutex2超时,释放mutex1");
lock1.unlock();
}
}
else if (lock2.try_lock()) {
safe_print("成功获取mutex2");
if (lock1.try_lock_until(timeout_point)) {
safe_print("成功获取mutex1");
break;
}
else {
safe_print("获取mutex1超时,释放mutex2");
lock2.unlock();
}
}
// 短暂等待后重试
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
// 如果成功获取了锁,执行操作
if (lock1.owns_lock() && lock2.owns_lock()) {
safe_print("执行关键操作...");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
else {
safe_print("未能获取所有锁,执行备选方案");
}
// RAII自动释放锁
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
safe_print("操作耗时: " + std::to_string(duration.count()) + "ms");
}
// 3. 多线程超时锁定演示
void multi_thread_timeout_demo() {
safe_print("\n===== 多线程超时锁定演示 =====");
// 工作线程函数
auto worker = [](int id) {
std::unique_lock<std::timed_mutex> lock1(mutex1, std::defer_lock);
std::unique_lock<std::timed_mutex> lock2(mutex2, std::defer_lock);
auto start = std::chrono::steady_clock::now();
auto timeout = std::chrono::milliseconds(100 + id * 20); // 不同线程不同超时
safe_print("线程 " + std::to_string(id) + ": 尝试在" +
std::to_string(timeout.count()) + "ms内获取锁");
// 随机决定锁定顺序
static std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<int> dist(0, 1);
bool first_lock_mutex1 = dist(rng) == 0;
if (first_lock_mutex1) {
if (lock1.try_lock_for(timeout)) {
safe_print("线程 " + std::to_string(id) + ": 成功获取mutex1");
if (lock2.try_lock_for(timeout)) {
safe_print("线程 " + std::to_string(id) + ": 成功获取mutex2");
safe_print("线程 " + std::to_string(id) + ": 执行关键操作");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
else {
safe_print("线程 " + std::to_string(id) + ": 获取mutex2超时");
}
}
else {
safe_print("线程 " + std::to_string(id) + ": 获取mutex1超时");
}
}
else {
if (lock2.try_lock_for(timeout)) {
safe_print("线程 " + std::to_string(id) + ": 成功获取mutex2");
if (lock1.try_lock_for(timeout)) {
safe_print("线程 " + std::to_string(id) + ": 成功获取mutex1");
safe_print("线程 " + std::to_string(id) + ": 执行关键操作");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
else {
safe_print("线程 " + std::to_string(id) + ": 获取mutex1超时");
}
}
else {
safe_print("线程 " + std::to_string(id) + ": 获取mutex2超时");
}
}
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
safe_print("线程 " + std::to_string(id) + ": 操作耗时 " +
std::to_string(duration.count()) + "ms");
};
// 创建并运行多个线程
std::vector<std::thread> threads;
for (int i = 0; i < 5; i++) {
threads.emplace_back(worker, i);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
}
// 4. 死锁避免演示
void deadlock_avoidance_demo() {
safe_print("\n===== 死锁避免演示 =====");
// 线程A:先锁mutex1,再锁mutex2
std::thread threadA([] {
safe_print("线程A: 尝试锁定mutex1");
std::lock_guard<std::timed_mutex> lock1(mutex1);
safe_print("线程A: 成功锁定mutex1");
// 模拟工作
std::this_thread::sleep_for(std::chrono::milliseconds(100));
safe_print("线程A: 尝试锁定mutex2");
std::unique_lock<std::timed_mutex> lock2(mutex2, std::defer_lock);
// 尝试在50ms内获取mutex2
if (lock2.try_lock_for(std::chrono::milliseconds(50))) {
safe_print("线程A: 成功锁定mutex2");
safe_print("线程A: 执行关键操作");
}
else {
safe_print("线程A: 获取mutex2超时,执行备选方案");
}
});
// 线程B:先锁mutex2,再锁mutex1
std::thread threadB([] {
safe_print("线程B: 尝试锁定mutex2");
std::lock_guard<std::timed_mutex> lock2(mutex2);
safe_print("线程B: 成功锁定mutex2");
// 模拟工作
std::this_thread::sleep_for(std::chrono::milliseconds(150));
safe_print("线程B: 尝试锁定mutex1");
std::unique_lock<std::timed_mutex> lock1(mutex1, std::defer_lock);
// 尝试在50ms内获取mutex1
if (lock1.try_lock_for(std::chrono::milliseconds(50))) {
safe_print("线程B: 成功锁定mutex1");
safe_print("线程B: 执行关键操作");
}
else {
safe_print("线程B: 获取mutex1超时,执行备选方案");
}
});
threadA.join();
threadB.join();
}
int main() {
std::cout << "===== 超时锁演示程序 =====" << std::endl;
// 1. 基本超时锁定演示
basic_timeout_demo();
// 2. 高级超时锁定演示
advanced_timeout_demo();
// 3. 多线程超时锁定演示
multi_thread_timeout_demo();
// 4. 死锁避免演示
deadlock_avoidance_demo();
std::cout << "\n===== 演示结束 =====" << std::endl;
return 0;
}
超时锁关键机制详解
1. std::unique_lock
的延迟锁定
std::unique_lock<std::mutex> lock1(mutex1, std::defer_lock);
-
std::defer_lock
参数表示创建锁对象时不立即锁定互斥量 -
允许稍后使用
lock()
,try_lock()
,try_lock_for()
,try_lock_until()
等方法锁定
2. 相对时间超时锁定
lock1.try_lock_for(std::chrono::milliseconds(100));
-
尝试在指定的相对时间内获取锁
-
返回
true
表示成功获取锁,false
表示超时 -
时间单位可以是
std::chrono::milliseconds
,seconds
,minutes
等
3. 绝对时间超时锁定
auto timeout_point = start + std::chrono::milliseconds(150);
lock2.try_lock_until(timeout_point);
-
尝试在指定的绝对时间点前获取锁
-
适用于需要精确控制超时时间的场景
-
返回
true
表示成功获取锁,false
表示超时
4. 同时尝试多个锁
if (std::try_lock(lock1, lock2) == -1) {
// 成功获取所有锁
}
-
std::try_lock
尝试同时锁定多个互斥量 -
返回
-1
表示成功获取所有锁 -
返回非负整数表示第几个锁获取失败
-
避免死锁风险
5. 超时处理与资源释放
if (lock1.owns_lock()) lock1.unlock();
-
检查锁是否被当前线程持有
-
确保在超时或异常情况下释放已获取的资源
-
防止资源泄漏和死锁
超时锁的最佳实践
-
合理设置超时时间:
-
根据系统响应要求和资源特性设置
-
避免过短(频繁超时)或过长(失去意义)
-
-
使用RAII管理锁:
{ std::unique_lock<std::mutex> lock(mutex, std::defer_lock); if (lock.try_lock_for(timeout)) { // 临界区操作 } } // 自动解锁
-
实现备选方案:
if (!lock.try_lock_for(timeout)) { // 执行备选操作 safe_print("超时,执行备选方案"); }
-
避免嵌套超时锁:
-
嵌套使用超时锁会增加复杂性
-
考虑使用更高级的同步机制
-
-
监控和日志:
-
记录超时事件和发生频率
-
分析系统性能瓶颈
-
超时锁的应用场景
-
实时系统:保证系统响应时间
-
网络服务:避免因资源争用导致服务不可用
-
资源管理:当多个资源需要同时锁定时
-
死锁预防:打破潜在的循环等待条件
-
高并发系统:管理大量线程对有限资源的访问
超时锁是多线程编程中的重要工具,合理使用可以显著提高系统的健壮性和响应能力。在实际开发中,应根据具体场景选择合适的超时策略和实现方式。
更多推荐
所有评论(0)