锁的种类
互斥锁、条件锁、自旋锁、读写锁、递归锁
1、互斥锁mutex
头文件:#include <mutex> // std::mutex
成员方法:lock()、try_lock()、unlock()
try_lock:1)所有线程都没有lock时,调用lock,并返回true;2)其他线程lock时返回false;3)本线程已lock,行为未定义。
unlock:要跟lock对应,如果没有lock而调用unlock,行为未定义。
1.1 lock_guard
建议使用lock_guard替代直接使用mutext.lock()、mutext.unlock(),lock_guard会在构造方法里mutext.lock(),析构方法里mutext.unlock(),主要为了避免中途异常退出导致mutext没有unlock。
{
std::lock_guard lg(m_mutex);
...
} //出了作用域后自动调用lock_guard的析构方法
1.2 unique_lock
lock_guard只能在析构的时候解锁,不提供unlock接口,不够方便,unique_lock提供lock和unlock接口,更灵活
std::unique_lock<std::mutex>guard(_mu);
//do something 1
guard.unlock();//临时解锁
//do something 2
guard.lock();//继续上锁
// do something
// 结束时析构guard会临时解锁
unique_lock比lock_guard灵活很多,效率上差一点,内存占用多一点。
lock_guard和unique_lock构造方法中一些参数说明:
defer_lock_t:do not acquire ownership of the mutex(初始化时不进行默认的上锁操作,后续需要上锁时再lock)
try_to_lock_t:try to acquire ownership of the mutex without blocking(初始化时调用try_lock)
adopt_lock_t:assume the calling thread already has ownership of the mutex(初始化时假定当前线程已经上锁成功,不再调用lock()函数)
2、条件锁
头文件:< condition_variable >
类型:std::condition_variable(只和std::mutex一起工作) 和 std::condition_variable_any(符合类似互斥元的最低标准的任何东西一起工作)
成员方法:wait、wait_for、wait_until、notify_one、notify_all
条件锁即条件变量,某一个线程因为某个条件未满足时可以使用条件变量使该线程处于阻塞状态,一旦条件满足以“信号量”的方式唤醒一个因为该条件而被阻塞的线程。常与互斥锁配配合使用。
void wait (unique_lock<mutex>& lck, Predicate pred);
pred说明:the function only blocks if pred returns false, and notifications can only unblock the thread when it becomes true (which is specially useful to check against spurious wake-up calls)。如果pred不为true,wait解锁互斥元,线程继续阻塞等待,下一次notify苏醒,再次检查条件,条件满足wait返回值,互斥元继续锁定。可以避免虚假唤醒
3、自旋锁
与互斥锁的相比,在获取锁失败的时候不会使得线程阻塞,而是一直自旋尝试获取锁。
自旋锁主要适用于被持有时间短,线程不希望在重新调度上花过多时间的情况。如果在持锁时间很长的场景下使用自旋锁,则会导致CPU在这个线程的时间片用尽之前一直消耗在无意义的忙等上,造成计算资源的浪费。
4、读写锁
任意读线程可以同时访问关键区域,但是只允许一个线程写入。
读写锁机制:
写者:写者使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者。
读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。
5、递归锁 std::recursive_mutex
Mutex可以分为递归锁(recursive mutex)和非递归锁(non-recursive mutex)。可递归锁也可称为可重入锁(reentrant mutex),非递归锁又叫不可重入锁(non-reentrant mutex)。
二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。
参考:
1、http://www.cplusplus.com/reference/mutex/unique_lock/
2、https://zhuanlan.zhihu.com/p/91062516?utm_source=wechat_timeline