std::lock_guard是 C++11 标准库提供的RAII 风格的互斥锁封装类 ,核心目的是自动管理互斥锁的加锁 / 解锁,从根本上避免 "忘记解锁导致死锁""异常导致锁无法释放" 这类低级且致命的错误。
一、先理解核心:RAII 设计思想
lock_guard的底层是RAII(资源获取即初始化) 机制 ------ 简单说:
- 构造时获取资源 :
lock_guard对象创建时,自动调用互斥锁的lock()方法加锁; - 析构时释放资源 :
lock_guard对象销毁时(比如出作用域),自动调用互斥锁的unlock()方法解锁。
这种 "自动性" 是解决多线程锁管理问题的关键,不用再手动记着unlock()。
二、lock_guard的基本用法(对比手动加解锁)
先看错误的手动加解锁(容易出问题):
cpp
#include <mutex>
std::mutex mtx;
void wrong_func() {
mtx.lock(); // 手动加锁
// 临界区操作:修改共享资源
int a = 10;
// 忘记解锁 → 死锁!
// 或如果临界区抛出异常,unlock()永远执行不到 → 死锁!
// mtx.unlock();
}
再看正确的 lock_guard 用法(自动解锁):
cpp
#include <mutex>
std::mutex mtx;
void right_func() {
// 创建lock_guard对象时,自动调用mtx.lock()
std::lock_guard<std::mutex> lock(mtx);
// 临界区:安全操作共享资源
int a = 10;
} // lock_guard对象出作用域,析构时自动调用mtx.unlock()
// 即使临界区抛出异常,析构函数也会执行 → 必解锁!
三、lock_guard的核心特性
1. 不可拷贝、不可移动
lock_guard被设计为 "只能在当前作用域使用",禁止拷贝 / 移动,避免锁的管理权被非法转移:
cpp
std::lock_guard<std::mutex> lock1(mtx);
// std::lock_guard<std::mutex> lock2 = lock1; // 编译错误:禁止拷贝
// std::lock_guard<std::mutex> lock3(std::move(lock1)); // 编译错误:禁止移动
2. 作用域决定解锁时机
lock_guard的解锁时机完全由作用域 控制,你可以通过{}手动限定作用域,精准控制解锁时机:
cpp
void func() {
{
std::lock_guard<std::mutex> lock(mtx);
// 短临界区:只保护必要的代码
shared_data = 100;
} // 此处提前解锁,不影响后续非临界区代码
// 非临界区:无需持锁,提升并发效率
sleep(1);
}
3. 轻量级、无额外开销
lock_guard是极简封装,没有额外的成员函数(比如unlock()/lock()),运行时几乎无性能损耗,适合简单的临界区保护。
4. 异常安全
这是lock_guard最核心的优势之一:即使临界区抛出异常,C++ 的异常机制会保证局部对象的析构函数执行,从而确保锁被释放:
cpp
void func_with_exception() {
std::lock_guard<std::mutex> lock(mtx);
// 临界区抛出异常
throw std::runtime_error("出错了");
// 无需手动unlock,析构函数会处理
}
int main() {
try {
func_with_exception();
} catch (...) {
// 捕获异常后,锁已经被释放,其他线程可正常获取
}
return 0;
}
四、lock_guard vs unique_lock(选型参考)
lock_guard是 "轻量版" 锁管理,std::unique_lock是 "功能版",两者的核心区别如下:
| 特性 | std::lock_guard |
std::unique_lock |
|---|---|---|
| 自动加解锁 | ✅ 支持 | ✅ 支持 |
手动解锁(unlock()) |
❌ 不支持 | ✅ 支持 |
配合条件变量(cv.wait()) |
❌ 不支持 | ✅ 支持 |
| 性能 | 极致轻量 | 略重(有额外状态) |
| 适用场景 | 简单临界区保护 | 复杂同步(如条件变量、手动控制解锁) |
简单说:
- 只要是 "加锁后,作用域结束解锁" 的简单场景,优先用
lock_guard(轻量、高效); - 如果需要手动解锁、配合条件变量(比如
cv.wait()需要解锁后阻塞),用unique_lock。
五、完整示例:lock_guard 保护共享资源
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
std::mutex mtx;
int shared_count = 0; // 共享资源
// 线程函数:累加共享变量
void increment(int n) {
for (int i = 0; i < n; ++i) {
// lock_guard自动加锁/解锁
std::lock_guard<std::mutex> lock(mtx);
shared_count++;
// 出循环迭代的作用域,自动解锁
}
}
int main() {
std::vector<std::thread> threads;
// 创建10个线程,每个线程累加1000次
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment, 1000);
}
// 等待所有线程结束
for (auto& t : threads) {
t.join();
}
// 正确输出10000,无数据错乱
std::cout << "最终count值:" << shared_count << std::endl;
return 0;
}
总结
std::lock_guard是 C++11 的 RAII 锁封装,构造加锁、析构解锁,核心解决 "忘记解锁 / 异常导致锁泄漏" 的问题;- 轻量级、异常安全、不可拷贝,适合简单临界区的线程安全保护;
- 解锁时机由作用域决定,可通过
{}手动缩小作用域,提升并发效率; - 复杂同步场景(如条件变量)用
unique_lock,简单场景优先用lock_guard。