C++的锁 | RAII管理锁 | 死锁避免
mutex、timed_mutex、recursive_mutex、RAII管理锁、lock_guard、unique_lock、死锁避免lock、try_lock、
一、基础互斥锁
基础互斥锁是同步的核心,封装了系统原生互斥锁,提供 “排他性所有权”—— 同一时间仅一个线程能持有锁,其他线程尝试加锁会阻塞或直接失败。
核心基础锁:std::mutex
最常用的互斥锁,排他性、非递归(同一线程不能多次加锁)。
核心成员函数
(1)lock()
- 原型:
void lock(); - 参数:无。
- 返回值:无。
- 核心作用:阻塞式加锁。若锁当前未被占用,当前线程会立即持有锁;若锁已被其他线程占用,当前线程会阻塞,直到锁被释放;若当前线程已持有该锁(非递归特性),会直接导致死锁。
(2)unlock()
- 原型:
void unlock(); - 参数:无。
- 返回值:无。
- 核心作用:释放锁。仅持有锁的线程能调用,否则行为未定义;释放后,所有阻塞在该锁上的线程会进入竞争状态,胜者获取锁。
(3)try_lock()
- 原型:
bool try_lock(); - 参数:无。
- 返回值:布尔类型(
true/false)。true:加锁成功(锁未被占用,当前线程持有锁);false:加锁失败(锁已被其他线程占用)。
- 核心作用:非阻塞式加锁。尝试加锁,成功则进入临界区,失败直接返回,不阻塞当前线程(适用于 “尝试访问,失败则做其他事” 的场景)。
关键规则
- 锁的生命周期:锁被销毁时,必须未被任何线程持有,否则行为未定义;
- 传参注意:线程函数中传递
std::mutex时,必须用std::ref(mtx)—— 线程构造会将参数拷贝到内部结构体,ref能保证传递的是原锁的引用(否则每个线程操作独立拷贝,无法同步)。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
// 共享资源(临界区)
int g_count = 0;
// 互斥锁:保护临界区
mutex g_mtx;
// 线程函数:累加共享变量(必须用ref传锁和共享变量)
void AddCount(size_t n, int& count, mutex& mtx) {
mtx.lock(); // 阻塞加锁,独占临界区
for (size_t i = 0; i < n; ++i) {
++count; // 无锁时会因多线程竞争导致结果错误
}
mtx.unlock(); // 释放锁,允许其他线程竞争
}
int main() {
// 创建两个线程,分别累加100万和200万(用ref传引用)
thread t1(AddCount, 1000000, ref(g_count), ref(g_mtx));
thread t2(AddCount, 2000000, ref(g_count), ref(g_mtx));
t1.join();
t2.join();
cout << "最终计数(无数据竞争):" << g_count << endl; // 正确输出3000000
return 0;
}
带超时的互斥锁:std::timed_mutex
继承 std::mutex 的所有特性,额外提供超时加锁接口,解决 “永久阻塞加锁” 的问题(避免线程因锁未释放而一直阻塞)。
新增核心成员函数(继承 lock/unlock/try_lock)
(1)try_lock_for()
- 原型:
template <class Rep, class Period> bool try_lock_for(const chrono::duration<Rep,Period>& rel_time); - 参数:
rel_time—— 相对超时时间(std::chrono::duration类型),如chrono::seconds(1)(1 秒)、chrono::milliseconds(500)(500 毫秒)。 - 返回值:布尔类型(
true/false)。true:超时前成功获取锁;false:超时后仍未获取锁(直接返回,不阻塞)。
- 核心作用:限时阻塞加锁。尝试加锁,若锁未被占用则立即持有;若已被占用,阻塞
rel_time时长,超时仍未获取则返回失败。
(2)try_lock_until()
- 原型:
template <class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock,Duration>& abs_time); - 参数:
abs_time—— 绝对超时时间点(std::chrono::time_point类型),如 “2026-01-19 10:00:00”。 - 返回值:布尔类型(
true/false)。true:到达abs_time前成功获取锁;false:到达abs_time仍未获取锁。
- 核心作用:限时阻塞加锁(按绝对时间点)。适用于 “必须在某个时间点前完成加锁” 的场景(如定时任务)。
样例:std::timed_mutex 超时加锁
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
timed_mutex g_tmtx;
void TryLockWithTimeout(int thread_id) {
// 尝试加锁,最多阻塞1秒(相对时间)
if (g_tmtx.try_lock_for(chrono::seconds(1))) {
cout << "线程" << thread_id << ":加锁成功,执行临界区操作" << endl;
this_thread::sleep_for(chrono::seconds(2)); // 模拟临界区执行
g_tmtx.unlock(); // 释放锁
} else {
cout << "线程" << thread_id << ":加锁超时(1秒),放弃执行" << endl;
}
}
int main() {
thread t1(TryLockWithTimeout, 1);
thread t2(TryLockWithTimeout, 2); // t1持有锁2秒,t2会超时
t1.join();
t2.join();
return 0;
}
运行结果
线程1:加锁成功,执行临界区操作
线程2:加锁超时(1秒),放弃执行
递归互斥锁:std::recursive_mutex
继承 std::mutex 的所有特性,唯一区别是支持递归加锁(同一线程可多次加锁,需对应多次解锁),解决 “线程内部嵌套加锁” 的场景。
核心特性
- 递归所有权:同一线程可多次调用
lock(),每次加锁计数 + 1;解锁时需调用相同次数的unlock(),计数归 0 才释放锁; - 其他线程竞争:若锁被当前线程持有(计数 > 0),其他线程调用
lock()会阻塞,try_lock()返回false; - 适用场景:函数嵌套调用(如 A 函数加锁后调用 B 函数,B 函数也需要加同一把锁)。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
recursive_mutex g_rmtx;
int g_val = 0;
// 嵌套函数:需要加同一把锁
void NestedFunc() {
g_rmtx.lock(); // 第二次加锁(递归)
g_val += 10;
cout << "嵌套函数:val=" << g_val << endl;
g_rmtx.unlock(); // 第二次解锁
}
// 主函数:加锁后调用嵌套函数
void MainFunc() {
g_rmtx.lock(); // 第一次加锁
g_val += 5;
cout << "主函数:val=" << g_val << endl;
NestedFunc(); // 嵌套调用,再次加锁
g_rmtx.unlock(); // 第一次解锁(计数归0,释放锁)
}
int main() {
thread t(MainFunc);
t.join();
cout << "最终val:" << g_val << endl; // 输出15
return 0;
}
二、RAII 锁管理类(自动加锁 / 解锁)
RAII(资源获取即初始化)是 C++ 管理资源的核心思想,锁管理类通过 “构造函数加锁、析构函数解锁”,自动管理锁的生命周期,避免因异常 / 忘记解锁导致的死锁。核心包含 std::lock_guard(简单)和 std::unique_lock(灵活)。
简单 RAII 锁:std::lock_guard
功能纯粹,仅支持 “自动加锁 / 解锁”,无额外接口,适用于 “加锁后执行临界区,执行完自动解锁” 的简单场景。
普通构造(自动加锁)
- 原型:
explicit lock_guard(mutex_type& m); - 参数:
m—— 引用类型的互斥锁(std::mutex/timed_mutex/recursive_mutex均可)。 - 核心作用:构造时自动调用
m.lock()加锁;析构时自动调用m.unlock()解锁(无论是否抛出异常)。
接管构造(adopt_lock)
- 原型:
lock_guard(mutex_type& m, adopt_lock_t tag); - 参数:
m—— 已加锁的互斥锁;tag——std::adopt_lock(标记 “锁已加锁,仅接管管理”)。 - 核心作用:不自动加锁,仅接管已加锁的锁对象,析构时自动解锁(适用于 “手动加锁后,需要 RAII 保证解锁” 的场景)。
拷贝构造 / 赋值
- 原型:
lock_guard(const lock_guard&) = delete;(拷贝构造禁用);lock_guard& operator=(const lock_guard&) = delete;(拷贝赋值禁用)。 - 核心原因:避免多个锁管理类管理同一把锁,导致重复解锁。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex g_mtx;
int g_count = 0;
void AddCount(size_t n) {
// 构造时自动加锁,析构时自动解锁(即使for循环中抛出异常也会解锁)
lock_guard<mutex> lock(g_mtx);
for (size_t i = 0; i < n; ++i) {
++g_count;
}
// 函数结束,lock析构,自动解锁
}
// 接管已加锁的场景
void AdoptLockDemo() {
g_mtx.lock(); // 手动加锁
lock_guard<mutex> lock(g_mtx, adopt_lock); // 接管锁,析构时解锁
cout << "接管已加锁的锁对象,执行临界区" << endl;
}
int main() {
thread t1(AddCount, 1000000);
thread t2(AddCount, 2000000);
t1.join();
t2.join();
cout << "最终计数:" << g_count << endl; // 3000000
AdoptLockDemo();
return 0;
}
灵活 RAII 锁:std::unique_lock
功能最丰富的 RAII 锁管理类,支持 “延迟加锁、超时加锁、移动语义、手动加锁 / 解锁”,适用于复杂场景(如条件变量、动态决定是否加锁)。
构造函数
默认构造
- 原型:
unique_lock() noexcept; - 核心作用:创建空锁管理对象,不关联任何互斥锁,
owns_lock()返回false。
自动加锁构造
- 原型:
explicit unique_lock(mutex_type& m); - 参数:
m—— 互斥锁引用。 - 核心作用:构造时自动调用
m.lock()加锁,析构时自动解锁。
延迟加锁构造(defer_lock)
- 原型:
unique_lock(mutex_type& m, defer_lock_t tag) noexcept; - 参数:
m—— 互斥锁引用;tag——std::defer_lock(标记 “延迟加锁”)。 - 核心作用:仅关联锁对象,不自动加锁,后续需手动调用
lock()加锁(适用于 “条件满足才加锁” 的场景)。
(4)接管已加锁构造(adopt_lock)
- 原型:
unique_lock(mutex_type& m, adopt_lock_t tag); - 参数:
m—— 已加锁的互斥锁;tag——std::adopt_lock。 - 核心作用:接管已加锁的锁对象,析构时自动解锁(同
lock_guard的接管构造)。
(5)超时加锁构造(try_lock_for/try_lock_until)
- 原型 1(相对时间):
template <class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time); - 原型 2(绝对时间):
template <class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time); - 核心作用:构造时调用
try_lock_for/try_lock_until加锁,超时则不持有锁;析构时仅在持有锁时解锁。
核心成员函数
(1)lock()/unlock()
- 原型:
void lock();/void unlock(); - 核心作用:手动加锁 / 解锁(仅适用于 “延迟加锁” 或 “手动解锁后重新加锁” 的场景)。
(2)try_lock()
- 原型:
bool try_lock(); - 返回值:
true(加锁成功)/false(加锁失败)。 - 核心作用:手动非阻塞加锁。
(3)owns_lock() / operator bool()
- 原型:
bool owns_lock() const noexcept;/explicit operator bool() const noexcept; - 返回值:
true(当前持有锁)/false(未持有锁)。 - 核心作用:判断锁管理对象是否持有锁(如超时加锁后判断是否成功)。
(4)move 构造 / 赋值
- 原型:
unique_lock(unique_lock&& x);/unique_lock& operator=(unique_lock&& rhs) noexcept; - 核心作用:支持移动语义,转移锁的管理权(原对象变为 “空对象”,不再管理锁);拷贝构造 / 赋值被禁用。
样例:std::unique_lock 灵活管理锁
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
mutex g_mtx;
// 场景1:延迟加锁(条件满足才加锁)
void DeferLockDemo() {
unique_lock<mutex> lock(g_mtx, defer_lock); // 延迟加锁
cout << "是否持有锁:" << boolalpha << lock.owns_lock() << endl; // false
// 模拟条件判断
bool condition = true;
if (condition) {
lock.lock(); // 手动加锁
cout << "条件满足,加锁成功:" << lock.owns_lock() << endl; // true
// 临界区操作
}
// 析构时自动解锁(若持有锁)
}
// 场景2:超时加锁 + 移动语义
unique_lock<mutex> GetLockWithTimeout() {
unique_lock<mutex> lock(g_mtx, chrono::seconds(1)); // 超时1秒加锁
if (lock.owns_lock()) {
cout << "加锁成功,返回锁管理权" << endl;
return lock; // 移动构造,转移管理权
} else {
cout << "加锁超时,返回空锁" << endl;
return unique_lock<mutex>(); // 返回空锁
}
}
int main() {
DeferLockDemo();
thread t([]{
this_thread::sleep_for(chrono::seconds(2)); // 模拟锁被占用
});
auto lock = GetLockWithTimeout(); // 接收移动过来的锁
t.join();
return 0;
}
三、多锁协同函数(lock/try_lock)
当需要同时锁定多个锁时,直接逐个 lock() 可能导致死锁(如线程 1 锁 A 等 B,线程 2 锁 B 等 A)。C++11 提供 std::lock 和 std::try_lock 函数,专门解决 “多锁同步” 问题。
1. 多锁原子锁定:std::lock
函数模板,原子化锁定多个锁,避免死锁 —— 要么全部锁定成功,要么全部解锁并阻塞,直到所有锁可用。
原型
template <class Mutex1, class Mutex2, class... Mutexes>
void lock(Mutex1& a, Mutex2& b, Mutexes&... cde);
参数
多个互斥锁的引用(Mutex1/Mutex2 可是 std::mutex/timed_mutex 等任何锁类型)。
返回值
无。
核心作用
- 原子操作:锁定所有锁,若其中一个锁不可用,会先解锁已锁定的所有锁,再阻塞等待;
- 死锁避免:内部通过 “锁排序” 算法,确保多个线程按同一顺序锁定多个锁,杜绝死锁。
样例:std::lock 避免多锁死锁
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx_a, mtx_b;
// 线程1:需要同时锁a和b
void TaskA() {
std::lock(mtx_a, mtx_b); // 原子化锁两个锁
cout << "TaskA:同时锁定a和b,执行临界区" << endl;
mtx_a.unlock();
mtx_b.unlock();
}
// 线程2:同样需要锁a和b(顺序无关,std::lock自动排序)
void TaskB() {
std::lock(mtx_b, mtx_a); // 即使顺序相反,也不会死锁
cout << "TaskB:同时锁定b和a,执行临界区" << endl;
mtx_b.unlock();
mtx_a.unlock();
}
int main() {
thread t1(TaskA);
thread t2(TaskB);
t1.join();
t2.join();
return 0;
}
2. 多锁尝试锁定:std::try_lock
核心定位
函数模板,非阻塞尝试锁定多个锁,要么全部锁定成功,要么解锁已锁定的锁并返回失败锁的下标。
原型
template <class Mutex1, class Mutex2, class... Mutexes>
int try_lock(Mutex1& a, Mutex2& b, Mutexes&... cde);
参数
多个互斥锁的引用。
返回值
int 类型:
-1:所有锁都锁定成功;- 非负整数:第
n个锁(下标从 0 开始)锁定失败,已锁定的锁会自动解锁。
核心作用
非阻塞尝试锁定多个锁,失败时不阻塞,可立即处理其他逻辑(如重试、放弃)。
样例:std::try_lock 尝试多锁
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx_a, mtx_b;
void TryLockMulti() {
int ret = std::try_lock(mtx_a, mtx_b);
if (ret == -1) {
// 全部锁定成功
cout << "TryLockMulti:所有锁锁定成功" << endl;
mtx_a.unlock();
mtx_b.unlock();
} else {
// 第ret个锁锁定失败
cout << "TryLockMulti:第" << ret << "个锁锁定失败" << endl;
}
}
int main() {
// 先锁定mtx_a,让TryLockMulti失败
mtx_a.lock();
thread t(TryLockMulti);
t.join();
mtx_a.unlock();
return 0;
}
运行结果
TryLockMulti:第0个锁锁定失败
更多推荐

所有评论(0)