C++11/14/17中提供的mutex系列类型如下:
互斥量 |
C++版本 |
作用 |
mutex |
C++11 |
基本的互斥量 |
timed_mutex |
C++11 |
timed_mutex带超时功能。在规定的等待时间内,没有获取锁,线程不会一直阻塞,代码会继续执行 |
recursive_mutex |
C++11 |
递归锁,允许在同一个线程中同一个互斥量多次被 lock() ,用于可能被连续多次上锁(期间未解锁)的情况,效率要比mutex低 std::mutex 及其变种不允许同一个线程对互斥量多次上锁,而 std::recursive_mutex 则允许 |
recursive_timed_mutex |
C++11 |
带超时的,递归的,独占互斥量,允许同一个线程,同一个互斥量,多次被lock,用法和非递归的一样 |
shared_timed_mutex |
C++14 |
具有超时机制的可共享互斥量 |
shared_mutex |
C++17 |
shared_mutex的弟弟曾实现是操作系统提供的读写锁,在多线程对共享资源读且少许县城对共享资源写的情况下,shared_mutex比mutex效率更高 写锁(排它锁):lock/unlock 读锁(共享锁):lock_shared/unlock_shared |
以上系列的对象都提供了加锁(lock)、尝试加锁(try_lock)和解锁(unlock)方法。
为了避免死锁,std::mutex.lock方法和std:mutex.unlock方法需要成对使用,如果一个函数中有很多出口,而互斥体对象又是需要在整个面数作用域被保护的资源,那么我们在编码时会因为忘记在某个出口处调用std::mutex.unlock 而造成死锁。这时可以通过RAII技术封装这两个接口,C++新标准也提为我们提供了类似的封装:
互斥量管理 |
C++版本 |
作用 |
lock_guard |
C++11 |
基于作用于的互斥量管理,在需要对资源进行保护的小范围作用域内,应首先考虑使用std::lock_guard |
unique_lock |
C++11 |
unique_lock 是 lock_guard 的升级加强版,一个通用的互斥量锁定包装器,它允许延迟锁定,限时深度锁定,递归锁定,锁定所有权的转移以及与条件变量一起使用。 |
shared_lock |
C++14 |
shared_lock可用于保护共享数据不被多个线程同时访问 unique_lock专门管理“独占模式”,而shared_lock专门管理“共享模式” |
scoped_lock |
C++17 |
如果有多个mutex要同时lock,用scoped_lock。如果只要lock一个mutex,可以用lock_guard |
如std::lock_guard:
void func() { std::lock quard<std:nutex> quard(mymutex); //在这里放被保护的资源操作 }
mymutex 的类型是std:mutex,guard对象的构造函数会自动调用mymutex.lock 方法对 mymutex 进行加锁,在 guard 对象出了其作用域时,guard对象的析构函数会自动调用 mymutex.unlock 方法对 mymutex 进行解锁,简单来说:根据 RAII原则,在构造函数中上锁(创建即上锁),在析构函数中解锁(销毁即解锁)。需要注意的是:mymutex 生命周期必须长于func 函数的作用域。
多线程使用锁经验总结:
- 减少锁的使用次数,能不用锁尽量不用;
- 明确锁的范围;
- 减少锁的使用粒度,尽量减少锁的作用的临界区代码范围。