mutex基础用法
创建std::mutex:
对mutex加锁:
对mutex解锁:
以上是 std::mutex 的基础用法,需要显式地调用 lock() 和 unlock() 来加锁和解锁。这种方式容易因异常或提前返回而导致忘记解锁。为了避免这种风险,可以使用基于 RAII(资源获取即初始化)思想的锁对象,在创建时自动加锁,并在离开作用域时自动解锁,从而确保锁的正确释放。
使用std::lock_guard
std::lock_guard 是最简单的 RAII 锁封装,构造时自动上锁,析构时自动解锁:
1 2 3 4 5 6
| std::mutex mtx;
void safe_print() { std::lock_guard<std::mutex> lock(mtx); std::cout << "thread-safe printing\n"; }
|
推荐在函数作用域内使用
使用std::unique_lock
std::unique_lock 功能更强大,支持延迟上锁、手动解锁、重新加锁、条件变量等。如果没有解锁,则在离开锁的作用域时会自动解锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| std::mutex mtx;
void example() { std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
lock.lock();
lock.unlock();
lock.lock(); }
|
std::unique_lock 是 std::condition_variable 唯一支持的锁类型。
这是它最重要的用途之一:允许线程等待条件满足时再继续执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| std::mutex mtx; std::condition_variable cv; bool ready = false;
void worker() { std::unique_lock<std::mutex> lock(mtx); std::cout << "工作线程开始等待\n"; cv.wait(lock, [] { return ready; }); std::cout << "工作线程开始执行\n"; }
void notifier() { std::this_thread::sleep_for(std::chrono::seconds(3)); { std::lock_guard<std::mutex> lock(mtx); ready = true; } cv.notify_one(); }
|
使用std::scoped_lock
std::scoped_lock 是 cpp17 引入的一种更现代的锁管理工具,用于简化多互斥量(std::mutex)的加锁操作,并且可以自动避免死锁。
单个锁:
1 2 3 4 5 6
| std::mutex mtx;
void example() { std::scoped_lock lock(mtx); }
|
多个锁(自动避免死锁)
1 2 3 4 5 6 7 8 9
| void f1() { std::scoped_lock lock(m1, m2); std::cout << "线程1获取 m1, m2\n"; }
void f2() { std::scoped_lock lock(m1, m2); std::cout << "线程2获取 m1, m2\n"; }
|
std::scoped_lock 会自动避免死锁
死锁通常发生在多个线程同时试图获取多个互斥锁,且加锁顺序不一致的情况下:
1 2 3 4 5 6 7 8 9 10 11
| std::mutex m1, m2;
void ThreadA() { std::lock_guard<std::mutex> lock1(m1); std::lock_guard<std::mutex> lock2(m2); }
void ThreadB() { std::lock_guard<std::mutex> lock1(m2); std::lock_guard<std::mutex> lock2(m1); }
|
std::scoped_lock 底层实现是基于 std::lock(),而 std::lock() 是一个专门为避免死锁设计的函数。std::lock() 打破了不可剥夺条件,采用“全拿全放”策略,核心思想是尝试性加锁 + 回退重试。
[!WARNING]
注意:条件变量必须使用 std::unique_lock,不能用 std::scoped_lock。
读写锁
cpp17引入了 std::shared_mutex 和 std::shared_lock 来实现“读写锁”机制。包含于头文件<shared_mutex>中。
写线程(独占锁):
写线程必须使用 std::unique_lock 或 std::lock_guard 或 std::scoped_lock 来加独占锁:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <shared_mutex> #include <thread> #include <iostream>
std::shared_mutex smtx; int counter = 0;
void writer() { std::unique_lock<std::shared_mutex> lock(smtx); ++counter; std::cout << "写线程修改 counter = " << counter << "\n"; }
|
读线程(共享锁):
读线程可以使用 std::shared_lock 来加共享锁:
1 2 3 4
| void reader(int id) { std::shared_lock<std::shared_mutex> lock(smtx); std::cout << "读线程 " << id << " 读取 counter = " << counter << "\n"; }
|