c++中try_lock怎么使用_c++多线程锁优化【进阶】

1次阅读

try_lock 是非阻塞加锁接口,立即返回布尔值表示是否成功;而 lock 会阻塞线程直至获取锁。本质区别在于是否挂起线程及将加锁决策权交予调用者。

c++中try_lock怎么使用_c++多线程锁优化【进阶】

try_lock 是什么,和 lock 有什么本质区别

try_lockstd::mutexstd::shared_mutexc++17)以及 std::unique_lock/std::shared_lock 等可移动锁管理器提供的非阻塞加锁接口。它不等待,而是立即返回一个 bool:成功拿到锁返回 true,否则返回 false,线程继续执行——这和 lock() 会一直挂起线程直到获取锁形成鲜明对比。

关键点在于:它把“是否加锁成功”的决策权交还给调用者,适合避免死锁、实现超时重试、或在关键路径上减少线程阻塞。

直接对 std::mutex 调用 try_lock 的典型用法

注意:std::mutextry_lock()public 成员函数,但只能由**当前未持有该锁的线程**调用;若已持有,行为未定义(多数实现会 abort 或抛异常)。

  • 必须确保调用前该线程没锁住这个 std::mutex,否则 UB
  • 返回 true 后,必须手动配对调用 unlock();不能依赖 RaiI 自动释放
  • 不推荐裸用 try_lock() + unlock(),容易漏掉解锁或提前 return 导致泄漏

示例:

立即学习C++免费学习笔记(深入)”;

std::mutex mtx; if (mtx.try_lock()) {     // 拿到锁,临界区     do_work();     mtx.unlock(); // 必须显式 unlock } else {     // 没抢到,走降级逻辑(如重试、跳过、记录日志)     fallback_strategy(); }

用 std::unique_lock 构造时传 std::defer_lock,再调用 try_lock

这才是更安全、更常见的进阶用法。通过 std::defer_lock 告诉 std::unique_lock “先别锁”,后续用其 try_lock() 方法控制时机,同时保留 RAII 自动析构解锁能力。

  • std::unique_lock<:mutex> lk{mtx, std::defer_lock}; 不加锁,也不抛异常
  • lk.try_lock() 返回 bool,成功后 lk有锁,离开作用域自动释放
  • 支持移动、条件判断、与 std::condition_variable 配合等高级操作
  • 多个锁顺序不确定时,可用 std::scoped_lock(C++17)替代,它内部就基于 try_lock 实现无死锁批量加锁

示例(避免双锁死锁):

std::mutex mtx1, mtx2; std::unique_lock lk1{mtx1, std::defer_lock}; std::unique_lock lk2{mtx2, std::defer_lock};  // 尝试按固定顺序加锁,失败则放弃重试(或换策略) if (lk1.try_lock() && lk2.try_lock()) {     // 安全访问共享资源     update_shared_data(); } else {     // 可选择:sleep 后重试、改用读锁、或转为只读快照 }

try_lock 在多线程性能优化中的真实代价与陷阱

很多人以为 try_lock 是“零成本”优化,其实不然。它仍需原子操作检查锁状态,在高争用场景下频繁调用反而比阻塞式 lock() 更耗 CPU(忙等)。是否划算,取决于你的等待预期:

  • 预期绝大多数时候能立刻拿到锁 → try_lock + 分支处理更高效
  • 预期经常要等 → 直接 lock() 让内核调度更省电,也避免 cache line 频繁 bouncing
  • try_lock 不提供超时,需要等待+防死锁请用 try_lock_for()try_lock_until()
  • 调试时注意:try_lock 失败不会抛异常,也不会打印日志,容易掩盖并发问题

真正容易被忽略的是:try_lock 的语义是“尽力而为”,不是“保证不阻塞”。如果临界区逻辑本身不可中断(比如必须更新某个计数器),那用 try_lock 就引入了业务逻辑分支,得额外设计 fallback 行为——这部分复杂度常被低估。

text=ZqhQzanResources