C++ 互斥锁
互斥锁(Mutual Exclusion,简称 mutex)是一种同步原语,用于保护共享资源,防止多个线程同时访问,从而避免数据竞争和不一致。优先使用RAII包装器// 好:异常安全// 临界区代码// 不好:手动管理容易出错mtx.lock();// 如果这里抛出异常,锁不会被释放!保持锁的粒度尽可能小// 好:锁粒度小// 非临界区操作// 更多非临界区操作// 不好:锁粒度太大// 锁定时间
·
C++ 互斥锁
目录
- 互斥锁的基本概念
- stdmutex-详解
- stdlock_guard-详解
- stdunique_lock-详解
- stdrecursive_mutex-详解
- stdtimed_mutex-详解
- 死锁预防与最佳实践
1. 互斥锁的基本概念
什么是互斥锁?
互斥锁(Mutual Exclusion,简称 mutex)是一种同步原语,用于保护共享资源,防止多个线程同时访问,从而避免数据竞争和不一致。
为什么需要互斥锁?
#include <iostream>
#include <thread>
#include <vector>
int shared_counter = 0;
void increment_unsafe() {
for (int i = 0; i < 100000; ++i) {
++shared_counter; // 数据竞争!
}
}
int main() {
std::thread t1(increment_unsafe);
std::thread t2(increment_unsafe);
t1.join();
t2.join();
std::cout << "Final counter: " << shared_counter << std::endl;
// 结果可能不是200000,因为存在数据竞争
return 0;
}
2. std::mutex 详解
核心成员函数
2.1 lock() 函数
作用:获取互斥锁的所有权。如果互斥锁已被其他线程持有,则当前线程阻塞直到锁可用。
函数签名:void lock();
参数:无
返回值:void
注意事项:
- 不能递归调用(同一线程重复锁定会导致未定义行为)
- 必须与unlock()配对使用
- 可能抛出std::system_error异常
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_counter = 0;
void increment_safe() {
for (int i = 0; i < 100000; ++i) {
mtx.lock(); // 获取锁
++shared_counter;
mtx.unlock(); // 释放锁
}
}
int main() {
std::thread t1(increment_safe);
std::thread t2(increment_safe);
t1.join();
t2.join();
std::cout << "Final counter: " << shared_counter << std::endl; // 保证是200000
return 0;
}
易错点:
// 错误示例:异常安全問題
void risky_function() {
mtx.lock();
if (some_condition) {
throw std::runtime_error("Error!"); // 如果抛出异常,锁不会被释放!
// mtx.unlock() 不会被调用,导致死锁
}
mtx.unlock();
}
2.2 try_lock() 函数
作用:尝试获取互斥锁,不阻塞线程。立即返回获取结果。
函数签名:bool try_lock();
参数:无
返回值:
- true:成功获取锁
- false:锁已被其他线程持有
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::mutex mtx;
void try_lock_example(int id) {
for (int i = 0; i < 3; ++i) {
if (mtx.try_lock()) {
std::cout << "Thread " << id << " acquired the lock" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx.unlock();
return;
} else {
std::cout << "Thread " << id << " failed to acquire the lock" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
}
int main() {
std::thread t1(try_lock_example, 1);
std::thread t2(try_lock_example, 2);
t1.join();
t2.join();
return 0;
}
使用场景:
- 非阻塞的锁获取尝试
- 避免线程长时间阻塞
- 实现简单的自旋锁逻辑
2.3 unlock() 函数
作用:释放互斥锁的所有权。
函数签名:void unlock();
参数:无
返回值:void
注意事项:
- 必须由锁的持有者调用
- 调用未锁定的mutex的unlock()是未定义行为
- 必须与lock()或成功的try_lock()配对使用
// 正确使用示例
void correct_usage() {
mtx.lock();
try {
// 临界区代码
protected_operation();
mtx.unlock(); // 确保在所有路径上都释放锁
} catch (...) {
mtx.unlock(); // 异常时也要释放锁
throw;
}
}
3. std::lock_guard 详解
RAII风格的自动锁管理
作用:在构造时自动加锁,在析构时自动解锁,确保异常安全。
构造函数
explicit lock_guard(mutex_type& m); // 构造时锁定mutex
使用示例:
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
std::mutex mtx;
int shared_counter = 0;
void increment_with_guard() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 构造时自动加锁
++shared_counter;
// lock析构时自动解锁,即使抛出异常也会解锁
}
}
int main() {
std::thread t1(increment_with_guard);
std::thread t2(increment_with_guard);
t1.join();
t2.join();
std::cout << "Final counter: " << shared_counter << std::endl;
return 0;
}
优势:
- 异常安全:保证锁总是被释放
- 代码简洁:不需要手动调用unlock()
- 作用域明确:锁的生命周期与作用域绑定
注意事项:
- 锁的生命周期受作用域限制
- 不能手动解锁,只能在析构时解锁
- 不支持延迟锁定或条件变量
4. std::unique_lock 详解
更灵活的RAII锁管理
特点:比lock_guard更灵活,支持延迟锁定、条件变量、手动解锁等。
核心构造函数
unique_lock() noexcept; // 1. 默认构造,不管理任何mutex
explicit unique_lock(mutex_type& m); // 2. 构造时立即锁定
unique_lock(mutex_type& m, std::defer_lock_t t); // 3. 延迟锁定
unique_lock(mutex_type& m, std::try_to_lock_t t); // 4. 尝试锁定
unique_lock(mutex_type& m, std::adopt_lock_t t); // 5. 接管已锁定的mutex
4.1 基本用法
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void basic_unique_lock() {
std::unique_lock<std::mutex> lock(mtx); // 立即锁定
// 临界区代码
std::cout << "Thread " << std::this_thread::get_id() << " in critical section" << std::endl;
// 自动解锁
}
4.2 延迟锁定
void deferred_lock_example() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// 此时mutex未被锁定
// ... 执行一些非临界区操作
lock.lock(); // 手动锁定
// 临界区代码
// 自动解锁
}
4.3 尝试锁定
void try_lock_example() {
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
// 成功获取锁
std::cout << "Lock acquired successfully" << std::endl;
} else {
// 获取锁失败,执行替代逻辑
std::cout << "Failed to acquire lock, doing alternative work" << std::endl;
}
}
4.4 手动锁定/解锁
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void manual_lock_unlock() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// 手动控制锁定时机
std::cout << "Doing some non-critical work..." << std::endl;
lock.lock(); // 手动锁定
std::cout << "In critical section" << std::endl;
lock.unlock(); // 手动解锁
std::cout << "Doing more non-critical work..." << std::endl;
lock.lock(); // 再次锁定
std::cout << "Back in critical section" << std::endl;
// 自动解锁
}
4.5 与条件变量配合使用
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool finished = false;
void producer() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::unique_lock<std::mutex> lock(mtx);
data_queue.push(i);
std::cout << "Produced: " << i << std::endl;
lock.unlock();
cv.notify_one(); // 通知消费者
}
std::unique_lock<std::mutex> lock(mtx);
finished = true;
cv.notify_one();
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
// 等待条件满足,wait会自动释放锁并在唤醒时重新获取
cv.wait(lock, []{ return !data_queue.empty() || finished; });
if (finished && data_queue.empty()) break;
while (!data_queue.empty()) {
int data = data_queue.front();
data_queue.pop();
std::cout << "Consumed: " << data << std::endl;
}
}
}
核心成员函数详解
| 函数 | 作用 | 返回值 | 注意事项 |
|---|---|---|---|
lock() |
锁定关联的mutex | void | 如果已锁定会抛出异常 |
try_lock() |
尝试锁定 | bool | 非阻塞,立即返回结果 |
try_lock_for(duration) |
在指定时间内尝试锁定 | bool | 仅适用于timed_mutex |
try_lock_until(timepoint) |
在指定时间点前尝试锁定 | bool | 仅适用于timed_mutex |
unlock() |
解锁关联的mutex | void | 必须已持有锁 |
release() |
释放mutex所有权但不解锁 | mutex_type* | 需要手动管理解锁 |
owns_lock() |
检查是否持有锁 | bool | 判断锁状态 |
operator bool() |
检查是否持有锁 | bool | 同owns_lock() |
5. std::recursive_mutex 详解
可重入互斥锁
作用:允许同一线程多次锁定同一个互斥锁。
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex rec_mtx;
void recursive_function(int depth) {
if (depth <= 0) return;
std::lock_guard<std::recursive_mutex> lock(rec_mtx);
std::cout << "Depth: " << depth << std::endl;
recursive_function(depth - 1); // 递归调用,需要可重入锁
// 如果是std::mutex,这里会导致死锁
}
int main() {
std::thread t(recursive_function, 3);
t.join();
return 0;
}
使用场景:
- 递归函数中的临界区保护
- 可能被同一线程多次调用的函数
- 需要嵌套锁定的复杂逻辑
注意事项:
- 性能略低于std::mutex
- 锁定次数必须与解锁次数匹配
- 避免过度使用,可能表示设计问题
6. std::timed_mutex 详解
带超时功能的互斥锁
6.1 try_lock_for()
作用:在指定时间内尝试获取锁
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::timed_mutex timed_mtx;
void timed_lock_example(int id) {
auto timeout = std::chrono::milliseconds(100);
if (timed_mtx.try_lock_for(timeout)) {
std::cout << "Thread " << id << " acquired the lock" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
timed_mtx.unlock();
} else {
std::cout << "Thread " << id << " failed to acquire lock within timeout" << std::endl;
}
}
int main() {
std::thread t1(timed_lock_example, 1);
std::thread t2(timed_lock_example, 2);
t1.join();
t2.join();
return 0;
}
6.2 try_lock_until()
作用:在指定时间点前尝试获取锁
void try_until_example() {
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
if (timed_mtx.try_lock_until(deadline)) {
std::cout << "Lock acquired before deadline" << std::endl;
// ... 临界区代码
timed_mtx.unlock();
} else {
std::cout << "Failed to acquire lock before deadline" << std::endl;
}
}
7. 死锁预防与最佳实践
7.1 使用std::lock同时锁定多个互斥锁
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
void safe_lock_order(int id) {
// 使用std::lock同时锁定多个mutex,避免死锁
std::lock(mtx1, mtx2);
// 使用std::adopt_lock接管已锁定的mutex
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Thread " << id << " acquired both locks safely" << std::endl;
}
void unsafe_lock_order(int id) {
// 错误的顺序可能导致死锁
mtx1.lock();
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // 增加死锁概率
mtx2.lock(); // 可能在此处死锁
std::cout << "Thread " << id << " acquired both locks" << std::endl;
mtx2.unlock();
mtx1.unlock();
}
7.2 最佳实践总结
-
优先使用RAII包装器
// 好:异常安全 { std::lock_guard<std::mutex> lock(mtx); // 临界区代码 } // 不好:手动管理容易出错 mtx.lock(); // 如果这里抛出异常,锁不会被释放! mtx.unlock(); -
保持锁的粒度尽可能小
// 好:锁粒度小 void good_design() { // 非临界区操作 do_non_critical_work(); { std::lock_guard<std::mutex> lock(mtx); do_critical_work(); } // 更多非临界区操作 do_more_work(); } // 不好:锁粒度太大 void bad_design() { std::lock_guard<std::mutex> lock(mtx); // 锁定时间过长 do_non_critical_work(); do_critical_work(); do_more_work(); } -
避免嵌套锁定的死锁
// 使用std::lock避免死锁 void no_deadlock() { std::unique_lock<std::mutex> lock1(mtx1, std::defer_lock); std::unique_lock<std::mutex> lock2(mtx2, std::defer_lock); std::lock(lock1, lock2); // 同时锁定,避免死锁 } -
考虑使用std::scoped_lock(C++17)
// C++17推荐方式 void scoped_lock_example() { std::scoped_lock lock(mtx1, mtx2); // 自动处理多个mutex // 临界区代码 }
完整示例:线程安全的队列
#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <chrono>
template<typename T>
class ThreadSafeQueue {
private:
mutable std::mutex mtx;
std::queue<T> data_queue;
std::condition_variable data_cond;
public:
ThreadSafeQueue() = default;
void push(T value) {
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(std::move(value));
data_cond.notify_one();
}
bool try_pop(T& value) {
std::lock_guard<std::mutex> lock(mtx);
if (data_queue.empty()) {
return false;
}
value = std::move(data_queue.front());
data_queue.pop();
return true;
}
void wait_and_pop(T& value) {
std::unique_lock<std::mutex> lock(mtx);
data_cond.wait(lock, [this]{ return !data_queue.empty(); });
value = std::move(data_queue.front());
data_queue.pop();
}
bool empty() const {
std::lock_guard<std::mutex> lock(mtx);
return data_queue.empty();
}
size_t size() const {
std::lock_guard<std::mutex> lock(mtx);
return data_queue.size();
}
};
int main() {
ThreadSafeQueue<int> ts_queue;
// 生产者线程
std::thread producer( {
for (int i = 0; i < 10; ++i) {
ts_queue.push(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
// 消费者线程
std::thread consumer( {
for (int i = 0; i < 10; ++i) {
int value;
ts_queue.wait_and_pop(value);
std::cout << "Consumed: " << value << std::endl;
}
});
producer.join();
consumer.join();
return 0;
}
这份详细的C++互斥锁教程涵盖了从基础概念到高级用法的所有核心内容,包括各种锁类型的使用方法、注意事项和最佳实践。
更多推荐

所有评论(0)