多线程,异步、同步,互斥量和锁
使用库来创建和管理线程。
使用创建互斥锁,并通过使用lock_guard或unique_lock类来管理互斥锁。
使用来实现原子操作
使用<conditional_variable>来使用条件变量
常见互斥锁
std::mutex
:最基本的互斥锁类型,一次只允许一个线程拥有锁。std::recursive_mutex
:允许同一个线程多次递归地锁定和解锁同一个互斥锁。std::timed_mutex
:支持带超时的锁定操作的互斥锁。
lock_guard
std::lock_guard
是 C++ 标准库中的一个模板类,它的设计目的是自动管理互斥锁的锁定和解锁操作。std::lock_guard
的模板参数是互斥锁的类型,以下是一些常见的互斥锁类型:
std::mutex
:最基本的互斥锁类型,一次只允许一个线程拥有锁。std::recursive_mutex
:允许同一个线程多次递归地锁定和解锁同一个互斥锁。std::timed_mutex
:支持带超时的锁定操作的互斥锁。
std::lock_guard 的实例化语法如下:
std::lock_guard<Mutex> lock_guard_instance(mutex_instance);
这里,Mutex 是互斥锁的类型,mutex_instance 是互斥锁的一个具体实例。
std::lock_guard是一个模板类。在构造时自动获取锁,并在析构时自动释放锁,从而避免了因异常或早期返回导致的死锁问题。
注意:lock_guard不能手动进行锁的锁定和释放。
lock_guard能够处理临界区的问题
#include <mutex>
#include <iostream>
std::mutex mtx;
void safe_print(int value) {
std::lock_guard<std::mutex> lk(mtx);
// 临界区开始
std::cout << "Printed value: " << value << std::endl;
// 临界区结束,lk 在这里自动析构并释放锁
}
int main() {
safe_print(10);
safe_print(20);
return 0;
}
lock_guard能够接收第二个参数,例如std::adopt_lock。
adopt_lock表示这个互斥量已经锁定了(需要提前将互斥量锁定,否则会报错)
临界区:
临界区(Critical Section) 保证在某一时刻只有一个线程能访问数据的简便办法。其只能用于对象在同一进程里线程间的互斥访问。
unique_lock
unique_lock也是一个类模板,模板参数也是Mutex互斥锁。其与lock_guard的区别有:
-
unique_lock能够转移mutex,而lock_guard不能;[注意:虽然能够转移mutex的所有权,但是不能够复制]
-
unique_lock可以执行手动锁定和解锁的操作,而lock_guard不能;
unique_lock可以接收的第二个参数:
-
adopt_lock:标记互斥量已经加锁了
-
defer_lock:标记一个没有加锁的互斥量。即初始化一个没有加锁的mutex。
-
try_to_lock:尝试使用mutex的lock()去锁定这个mutex,如果没有锁定成功,返回false,函数不阻塞。
unique_lock的成员方法:
-
lock():加锁
-
unlock():解锁
-
try_lock():尝试给互斥量加锁,如果拿不到锁,返回flase;如果拿到了锁,返回true。此函数不阻塞。
-
release():返回管理的mutex对象指针,并释放所有权。如果原来mutex对象处于加锁状态,需要自己负责手动解锁。
conditional_variable
conditional_variable类是条件变量。当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。
std::condition_variable 对象通常使用 std::unique_lock<std::mutex>来等待,如果需要使用另外的 lockable 类型,可以使用 std::condition_variable_any 类。
锁的粒度
锁定区域的大小称为锁的粒度,锁定范围大称为粗粒度,锁定范围小称为细粒度。