在我们写多线程程序时,有些资源需要保证在同一时间内不会被多个线程访问和操作,这时候通常的做法是使用锁,具体的,对资源进行访问与操作的代码片段被称为临界区,通过锁的锁定,使得在同一时刻,只能有一个线程执行临界区的代码,这样保证了临界区代码的互斥性和原子性。
在 C++ 中,为我们提供了互斥锁,用于对临界区的锁定,基本的定义都在头文件中,下面我们来看看 C++ 中如何通过 mutex 实现对临界区的保护。
分类
头文件中提供了两种类型的同步工具,一种是直接的同步原语,包括:
- mutex:独占,非递归的互斥锁
- timed_mutex: 支持 timeout 的独占非递归的互斥锁
- recursive_mutex:支持独占,递归的互斥锁
- recursive_timed_mutex:支持 timeout 的独占递归的互斥锁
另一类是锁的包装类,包括:
- lock_guard:实现严格基于作用域的互斥锁所有权包装器
- unique_lock:实现可移动的互斥锁所有权包装器
- scoped_lock:用于多个互斥锁的免死锁 RAII 封装器
通常来说,我们应该多用包装类,少用同步原语,包装类为我们提供了较为完善的锁管理机制,防止我们忘记调用 unlock 或者调用 lock 出错导致的死锁和内存泄漏。本文首先对这几个互斥锁进行介绍。
同步原语
std::mutex
mutex 类是用于保护被多个线程同时访问的共享数据的同步原语。
mutex 类有三个成员函数,分别是:
- lock:获取锁的所有权,如果其他线程已经持有锁会阻塞调用线程
- try_lock:尝试获取锁的所有权,如果其他线程已经持有锁会返回 false
- unlock:释放锁的所有权
当调用线程调用 lock 方法或 try_lock 方法时,会获取到 mutex 的所有权,直到调用 unlock 方法释放锁;当一个线程已经获取了 mutex 的所有权时,其他线程再次调用 lock 时,会被阻塞在 lock 方法中,若是其他线程调用 try_lock 方法,则是会返回一个 false,表示有其他线程已经获取了锁。
std::timed_mutex
timed_mutex 类除了 mutex 类的 lock、try_lock 和 unlock 方法,提供了两个方法支持超时获取锁的所有权:
- bool try_lock_for(const std::chrono::duration<Rep,Period>& timeout_duration)
- bool try_lock_until(const std::chrono::time_point<Clock,Duration>& timeout_time)
当尝试获取锁的所有权超时之后,会返回 false
std::recursive_mutex
支持递归的互斥锁,当单个线程获取锁后,可以继续通过 try_lock 或 lock 方法进入临界区,在 recursive_mutex 中有个计数器用来记录 lock 的次数,当 unlock 调用相应的次数后,则会释放该递归锁,也可以称为可重入锁。
std::recursive_timed_mutex
recursive_timed_mutex 则是兼具 timed_mutex 和 recursive_mutex 的功能,支持超时退出的获取递归互斥锁。
示例
下面举个例子介绍上述几个 mutex 的使用。
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
class Speaking {
private:
int a;
std::mutex m;
std::timed_mutex t_m;
std::recursive_mutex r_m;
std::recursive_timed_mutex r_t_m;
public:
Speaking() : a(0){};
~Speaking() = default;
void speak_without_lock();
void speak();
void speak_timed_lock();
void speak_recursive_lock();
void speak_recursive_lock2();
void speak_lock_without_recursive_lock();
void speak_lock_without_recursive_lock2();
};
void Speaking::speak_without_lock() {
std::cout << std::this_thread::get_id() << ": " << a << std::endl;
a++;
}
void Speaking::speak() {
m.lock();
speak_without_lock();
m.unlock();
}
void Speaking::speak_timed_lock() {
if (t_m.try_lock_for(std::chrono::seconds(1))) {
std::this_thread::sleep_for(std::chrono::seconds(2));
speak_without_lock();
t_m.unlock();
} else {
std::cout << std::this_thread::get_id() << ": time_out" << std::endl;
}
}
void Speaking::speak_recursive_lock() {
r_m.lock();
speak_recursive_lock2();
r_m.unlock();
}
void Speaking::speak_recursive_lock2() {
r_m.lock();
speak_without_lock();
r_m.unlock();
}
void Speaking::speak_lock_without_recursive_lock() {
t_m.lock();
speak_lock_without_recursive_lock2();
t_m.unlock();
}
void Speaking::speak_lock_without_recursive_lock2() {
if (t_m.try_lock_for(std::chrono::seconds(1))) {
speak_without_lock();
t_m.unlock();
} else {
std::cout << std::this_thread::get_id() << ": time_out" << std::endl;
}
}
int main() {
Speaking s;
// std::cout << "speak without lock:" << std::endl;
std::thread t1(&Speaking::speak_without_lock, &s);
std::thread t2(&Speaking::speak_without_lock, &s);
t1.join();
t2.join();
std::cout << "speak with lock:" << std::endl;
std::thread t3(&Speaking::speak, &s);
std::thread t4(&Speaking::speak, &s);
t3.join();
t4.join();
std::cout << "speak with timed lock:" << std::endl;
std::thread t_t1(&Speaking::speak_timed_lock, &s);
std::thread t_t2(&Speaking::speak_timed_lock, &s);
t_t1.join();
t_t2.join();
std::cout << "speak with recursive lock:" << std::endl;
std::thread t_r1(&Speaking::speak_recursive_lock, &s);
std::thread t_r2(&Speaking::speak_recursive_lock, &s);
t_r1.join();
t_r2.join();
std::cout << "speak without recursive lock:" << std::endl;
std::thread t_r3(&Speaking::speak_lock_without_recursive_lock, &s);
t_r3.join();
return 0;
}
运行结果如下:
- 不使用 mutex 进行线程间同步时,a++ 由于不是原子的,会发生问题,
- 使用 mutex 对 a++ 进行上锁之后就不会有线程间同步问题
- 使用 timed_mutex 时,由于我们设置临界区中 sleep 了 2s,当另一个线程获取锁超时 1s 之后会自动返回 false
- 验证 recursive_mutex 时,我们设置了会对 mutex 多次上锁,为了不让程序死锁,我们使用 timed_mutex 进行对照,可以看到 recursive_mutex 支持同一个线程反复上锁,而 timed_mutex 不支持重复上锁,在第二次上锁时超时了。
小结
本文对 c++ 的同步原语互斥锁进行了介绍,后续对互斥锁的包装类进行详细的介绍。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于