1. 简述
在多线程或其他许多场景下,同时对一个变量或一段资源进行读写操作是一个比较常见的过程,保证数据的一致性和防止竞态条件至关重要。
C++的标准库中为我们提供了使用的互斥及锁对象,帮助我们实现资源的互斥操作。
2. std::mutex及其衍生互斥手段
(1)互斥类
(2)RAII上锁
(3)API
3. std::mutex使用
std::mutex是最简单的互斥量,可以单独使用为资源创建互斥环境,也可以与std::lock_guard合起来使用,实现一个RAII的应用。
需要注意的是,std::mutex仅支持一次加锁和解锁。如下是一个简单地小程序。
/** 引用头文件. */
#include <mutex>
/** 创建互斥量. */
std::mutex mtx;
/** 需要共享的某个资源 */
int sharedResource;
/** 操作上述共享资源的函数 */
void accessResource() {
/** 尝试加锁 */
mtx.lock();
/** 临界区开始 - 访问共享资源 */
sharedResource += 1;
/** 临界区结束 - 释放锁 */
mtx.unlock();
}
接下来是一个配合lock_guard使用的例程。
/** 引用头文件. */
#include <mutex>
/** 创建互斥量. */
std::mutex mtx;
/** 需要共享的某个资源 */
int sharedResource;
/** 操作上述共享资源的函数 */
void accessResource() {
/** 尝试加锁 */
std::lock_guard<std::mutex> guard(mtx); // 自动加锁
/** 临界区开始 - 访问共享资源 */
sharedResource += 1;
/** 退出函数,自动释放. */
}
4. std::recursive_mutex递归锁
从名字可以看出,递归所是可以多次上锁的,当然也需要配合多次解锁,通常情况下也仅用在递归环境下。
如下是简单的使用std::recursive_mutex的示例。
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex mtx;
void func(int n) {
mtx.lock();
std::cout << "Thread " << n << " locked the mutex" << std::endl;
if (n > 1) {
func(n - 1);
}
std::cout << "Thread " << n << " unlocked the mutex" << std::endl;
mtx.unlock();
}
int main() {
std::thread t1(func, 3);
std::thread t2(func, 2);
t1.join();
t2.join();
return 0;
}
如下是配合lock_guard使用的示例。
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex mtx;
void func(int n) {
std::lock_guard<std::recursive_mutex> guard(mtx);
std::cout << "Thread " << n << " locked the mutex" << std::endl;
if (n > 1) {
func(n - 1);
}
std::cout << "Thread " << n << " unlocked the mutex" << std::endl;
}
int main() {
std::thread t1(func, 3);
std::thread t2(func, 2);
t1.join();
t2.join();
return 0;
}
5. std::timed_mutex
std::timed_mutex 类似于 std::mutex,也是一个较为简单的锁。但是它额外提供了两个接口分别是 try_lock_for() 和 try_lock_until() 成员函数。前者允许线程尝试在一段时间内获取锁,如果在指定的时间内未能获得锁,线程将返回失败,并且可以根据返回值来判断是否继续等待或者执行其他逻辑。后者是一个确定的时间点,当到达指定的时间点以后,互斥锁不能够使用,则返回。
使用 std::timed_mutex 可以帮助避免线程因为获取锁时长时间阻塞而导致程序性能下降或死锁情况的发生。
6. std::lock_guard和std::unique_lock
std::lock_guard 和 std::unique_lock 都是 C++ 标准库中用于管理互斥量(mutex)的 RAII(Resource Acquisition Is Initialization,资源获取即初始化)包装器。它们都可以确保在持有互斥量的作用域内,互斥量会被安全地锁定和解锁,从而避免死锁和其他并发问题。不过,std::unique_lock 比 std::lock_guard 提供了更多的灵活性和功能。下面是它们的一些主要区别以及使用示例。
我们在前面第3节和第4节都列举了使用lock_guard的使用,lock_guard的优点是使用简单,缺点是过于简单了。
unique_lock能够实现和lock_guard一样的动能,也提供了更灵活的上锁和解锁控制。
unique_lock含有第二参数,如下所示:
unique_lock的成员函数如下。