C++标准库之mutex
互斥锁有可重入、不可重入之分。C++标准库中用mutex表示不可重入的互斥锁,用recursive_mutex表示可重入的互斥锁。为这两个类增加根据时间来阻塞线程的能力,就又有了两个新的互斥锁:timed_mutex(不可重入的锁)、recursive_timed_mutex(可重入的锁)。
互斥锁单独使用时主要是为了使对共享资源的互斥使用,即同时只能有一个线程使用,以防止同时使用可能造成的数据问题。
C++标准库的所有mutex都是不可拷贝的,也不可移动。
mutex基本操作
上锁 lock 如果mutex未上锁,则将其上锁。否则如果已经其它线程lock,则阻塞当前线程。
上锁 try_lock 如果mutex未上锁,则将其上锁。否则返回false,并不阻塞当前线程。
解锁 unlock 如果mutex被当前线程锁住,则将其解锁。否则,是未定义的行为。
timed_mutex在mutex的基础上增加了以下两个操作
try_lock_for(duration) 如果timed_mutex未上锁,则将其上锁,否则阻塞当前线程,但最长只阻塞duration表示的时间段。
try_lock_until(time_point) 如果timed_mutex未上锁,则将其上锁,否则阻塞当前线程,但最长只会阻塞到time_point表示的时间点就不再阻塞。
try_lock_for/until可以检测到死锁的出现,这是目前想到的一种用途。
if(!try_lock_for(chrono::hours(1))) { throw "出现死锁!"; }
可重入的锁 recursive_mutex、recursive_timed_mutex与对应的mutex、timed_mutex操作一致。不同点在于,不可重入的锁在lock或try_lock一个已经被当前线程lock的锁时会导致死锁,而可重入的锁不会。
辅助类
template<class Mutex> class lock_guard;
lock_guard用于脱离lock_guard对象生存期后自动对互斥锁进行解锁操作。
explicit lock_guard(mutex_type &m);对象创建时执行 m.lock(),对象销毁时执行 m.unlock()
explicit lock_guard(mutex_type &m,adpot_lock_t tag);对象创建不执行lock,对象销毁时执行 m.unlock()。所以m应该是一个已经被当前线程lock的互斥锁。
template<class Mutex> class unique_lock;
unique_lock()noexcept;不管理任何锁。
explicit unique_lock(mutex_type &m);对象创建时执行 m.lock()。
unique_lock(mutex_type &m,try_to_lock_t tag);对象创建时执行 m.try_lock()。
unique_lock(mutex_type &m,defer_lock_t tag);对象创建时不进行上锁操作,m要满足没有被当前线程锁住的条件。
unique_lock(mutex_type &m,adopt_lock_t tag);对象创建时不进行上锁操作,m要满足已经被当前线程锁住的条件。
unique_lock(mutex_type &m,const duration & real_time);对象创建时执行 m.try_lock_for(real_time)。
unique_lock(mutex_type &m,const time_point & abs_time);对象创建时执行 m.try_lock_until(abs_time)。
unique_lock(unique_lock &&);移动构造
操作:unique_lock具备它所管理的锁的所有操作 lock、unlock、try_lock、try_lock_for、try_lock_until。
mutex_type *release(); 不再管理互斥锁。
void swap(unique_lock &);交换管理的互斥锁。
bool owns_lock() 用于探测unique_lock是否管理着一个互斥锁且其处于上锁状态。bool operate bool() 与owns_lock等同。
mutex_type * mutex();用于返回管理的互斥锁的指针,但仍对其进行管理。
在unique_lock销毁的时候,owns_lock为真才会执行unlock。
总的来说,lock_guard在时空间效率上比较高,但功能单一。unique_lock功能多,使用灵活,但时空间效率不如lock_guard。如果使用了辅助类来管理互斥锁,就不要直接操作锁了,否则容易引发混乱,产生BUG。
辅助函数
template <class Mutex1, class Mutex2, class... Mutexes>
int try_lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
根据参数顺序对多个锁进行上锁,如果成功锁住所有锁,返回-1,返回值大于0表示失败的锁的位置号。
template <class Mutex1, class Mutex2, class... Mutexes>
void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
对多个锁进行上锁,该函数是阻塞的。另,它保证发生异常的情况下已经上锁的锁会被解锁。