在多线程的程序中,经常需要通过锁的机制来保证数据的一致性。
C++11
提供了下面四种语义的锁:
type | function | desc |
---|---|---|
mutex | Lockable | 普通的互斥锁,不能递归使用。 |
timed_mutex | TimedLockable | 带超时的互斥锁,不能递归使用。 |
recursive_mutex | Lockable | 递归互斥锁。 |
recursive_timed_mutex | TimedLockable | 带超时的递归互斥锁。 |
继承关系为:
1 | BasicLockable <--- Lockable <--- TimedLockable |
mutex
C++11
中最基本的互斥量,它不支持递归地上锁。
mutex
不允许拷贝构造,也不允许move
赋值,最初产生的mutex
对象是处于unlocked
状态的。lock()
,调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:- 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用
unlock
之前,该线程一直拥有该锁。 - 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
- 如果当前互斥量被当前调用线程锁住,则会产生死锁。
- 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用
unlock()
, 解锁,释放对互斥量的所有权。try_lock()
,尝试锁住互斥量,和lock()
相似,不同的是如果互斥量被其他线程占有,则当前线程也不会被阻塞,而是马上返回false
。
recursive_mutex
- 允许同一个线程对互斥量多次上锁,来获得对互斥量对象的多层所有权。
- 释放互斥量时需要调用与该锁层次深度相同次数的 unlock()。
time_mutex
- 比
mutex
多了两个成员函数:try_lock_for()
,try_lock_until()
。 try_lock_for()
函数接受一个时间范围,表示在 一段时间范围之内 线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回false
。try_lock_until()
函数则接受一个时间点作为参数,在 指定时间点未到来之前 线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回false
。
RAII
关于 mutex
的使用,建议使用 RAII
(Resource Acquisition is Initialization)的方式,即在构造的时候 lock
, 析构的时候 unlock
, 不建议直接手工地进行 lock/unlock
。
C++11
提供了 lock_guard
和 unique_lock
两种简单而又安全的上锁和解锁方式,即使程序抛出了异常,先前已被上锁的 Mutex
对象也能正确进行解锁,极大地简化了与 Mutex
相关的异常处理代码。
1 | template <class mutex> |
- 都是模版类,实例化参数
mutex
必须是个BasicLockable
类型(即支持lock
和unlock
)。 - 都只负责在构造时对
mutex
加锁,析构时对mutex
解锁,它们并不管理mutex
本身的生命周期,因此,mutex
的生命周期应至少延伸至lock_guard
和unique_lock
析构之后。
lock_guard
- 只有构造函数和析构函数。
unique_lock
- 更灵活的初始化方式:
- default
- locking
- try_locking
- deferred
- adopting
- locking_for
- locking_until
- move
- 除了基本的上锁/解锁操作,
unique_lock
还支持修改、获取mutex
的操作:move
移动赋值swap
与另一个unique_lock
对象交换它们所管理的Mutex
对象的所有权release
返回指向它所管理的Mutex
对象的指针,并释放所有权owns_lock
返回当前unique_lock
对象是否获得了锁operator bool()
与owns_lock
功能相同 5.mutex
返回当前unique_lock
对象所管理的Mutex
对象的指针