C++并发编程实战:深入理解互斥量(Mutex)的使用
互斥量基础概念
在多线程编程中,互斥量(Mutex)是最基本的同步机制之一,用于保护共享数据免受并发访问的破坏。C++11标准库提供了多种互斥量类型,满足不同场景下的线程同步需求。
std::mutex详解
std::mutex
是C++中最基本的互斥量类型,它提供了独占所有权的特性。理解std::mutex
的核心要点:
-
基本特性:
- 不支持递归上锁(同一线程多次锁定会导致死锁)
- 不可拷贝或移动
- 初始状态为未锁定(unlocked)
-
关键成员函数:
lock()
:阻塞式获取锁unlock()
:释放锁try_lock()
:非阻塞式尝试获取锁
使用示例
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_data = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
mtx.lock();
++shared_data;
mtx.unlock();
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final value: " << shared_data << std::endl;
return 0;
}
注意事项:
- 必须确保每次
lock()
都有对应的unlock()
- 忘记解锁会导致死锁
- 异常安全:如果
lock()
和unlock()
之间抛出异常,可能导致锁无法释放
递归互斥量 std::recursive_mutex
std::recursive_mutex
解决了同一线程需要多次获取同一锁的问题:
-
特点:
- 允许同一线程多次锁定
- 需要相同次数的解锁操作
- 性能略低于普通互斥量
-
适用场景:
- 递归函数调用
- 需要调用多个可能获取同一锁的函数
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex rmtx;
void recursive_function(int level) {
rmtx.lock();
std::cout << "Level " << level << " locked" << std::endl;
if (level > 0) {
recursive_function(level - 1);
}
std::cout << "Level " << level << " unlocked" << std::endl;
rmtx.unlock();
}
int main() {
std::thread t(recursive_function, 3);
t.join();
return 0;
}
定时互斥量 std::timed_mutex
std::timed_mutex
在普通互斥量基础上增加了超时功能:
-
新增功能:
try_lock_for()
:尝试在指定时间段内获取锁try_lock_until()
:尝试在指定时间点前获取锁
-
使用场景:
- 需要避免长时间阻塞的场合
- 实现带超时的资源访问
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
std::timed_mutex tmtx;
void timed_task(int id) {
using namespace std::chrono;
if (tmtx.try_lock_for(seconds(1))) {
std::cout << "Thread " << id << " got the lock" << std::endl;
std::this_thread::sleep_for(milliseconds(500));
tmtx.unlock();
} else {
std::cout << "Thread " << id << " couldn't get the lock" << std::endl;
}
}
int main() {
std::thread t1(timed_task, 1);
std::thread t2(timed_task, 2);
t1.join();
t2.join();
return 0;
}
锁保护类:std::lock_guard和std::unique_lock
为了避免手动管理锁的生命周期,C++提供了RAII风格的锁保护类。
std::lock_guard
最简单的锁保护机制,构造时加锁,析构时解锁。
特点:
- 不支持手动解锁
- 不支持锁的所有权转移
- 轻量级,性能好
std::mutex mtx;
void safe_increment() {
std::lock_guard<std::mutex> lock(mtx);
// 临界区代码
// 离开作用域自动解锁
}
std::unique_lock
更灵活的锁保护机制,提供更多控制选项。
特点:
- 支持延迟锁定
- 支持手动解锁
- 支持锁的所有权转移
- 支持条件变量
- 比lock_guard稍重
std::mutex mtx;
void flexible_task() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// 做一些不需要锁的工作...
lock.lock(); // 需要时手动锁定
// 临界区代码
lock.unlock(); // 可以手动解锁
// 做一些不需要锁的工作...
if (needs_lock_again) {
lock.lock();
// 更多临界区代码
}
// 离开作用域自动处理
}
最佳实践建议
- 优先使用RAII锁:尽可能使用
lock_guard
或unique_lock
而非直接操作mutex - 锁的粒度:保持锁的粒度尽可能小,减少持有锁的时间
- 避免死锁:
- 按固定顺序获取多个锁
- 使用
std::lock()
函数同时锁定多个互斥量
- 性能考虑:
- 只在必要时使用递归锁
- 评估锁竞争情况,考虑更高级的同步机制
通过合理使用这些互斥量类型和锁保护机制,可以构建出既安全又高效的并发程序。理解每种工具的特性和适用场景,是成为优秀C++并发程序员的关键一步。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考