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

try_lock 是什么,和 lock 有什么本质区别
try_lock 是 std::mutex、std::shared_mutex(c++17)以及 std::unique_lock/std::shared_lock 等可移动锁管理器提供的非阻塞加锁接口。它不等待,而是立即返回一个 bool:成功拿到锁返回 true,否则返回 false,线程继续执行——这和 lock() 会一直挂起线程直到获取锁形成鲜明对比。
关键点在于:它把“是否加锁成功”的决策权交还给调用者,适合避免死锁、实现超时重试、或在关键路径上减少线程阻塞。
直接对 std::mutex 调用 try_lock 的典型用法
注意:std::mutex 的 try_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 行为——这部分复杂度常被低估。