C++/Mutex
mutex 是C++标准程式库中的一个头文件,定义了C++11标准中的一些互斥访问的类与方法等。
互斥类
[编辑]标准库中的互斥锁类型可分为:
- BasicLockable类型,只需满足两种操作,lock 和 unlock
- Lockable 类型,在BasicLockable类型的基础上增加了try_lock操作
- TimedLockable类型,在Lockable类型的基础上增加了try_lock_for 和 try_lock_until 两种操作。
标准库定义了4个互斥类:
- std::mutex:该类表示普通的b:互斥锁。类成员包括:
- 构造函数,不允许拷贝构造,也不允许move构造,mutex对象初始化为unlocked状态;
- 成员函数:
- lock():线程申请该互斥锁。如果未能获得该互斥锁,则调用线程将阻塞(block)在该互斥锁上;如果成功获得该互诉锁,该线程一直拥有该互斥锁直到调用unlock解锁;如果该互斥锁已经被当前调用线程锁住,则产生死锁(deadlock)。
- unlock():解锁,释放调用线程对该互斥锁的所有权。
- try_lock():尝试获得该互斥锁。如果互斥锁被其他线程占有,则当前调用线程也不会被阻塞,而是由该函数调用返回false;如果该互斥锁已经被当前调用线程锁住,则会产生死锁。
- std::timed_mutex:该类表示定时互斥锁。std::time_mutex比std::mutex多了两个成员函数:
- try_lock_for():函数参数表示一个时间范围,在这一段时间范围之内线程如果没有获得锁则保持阻塞;如果在此期间其他线程释放了锁,则该线程可获得该互斥锁;如果超时(指定时间范围内没有获得锁),则函数调用返回false。
- try_lock_until():函数参数表示一个时刻,在这一时刻之前线程如果没有获得锁则保持阻塞;如果在此时刻前其他线程释放了锁,则该线程可获得该互斥锁;如果超过指定时刻没有获得锁,则函数调用返回false。
- std::recursive_mutex:该类表示递归互斥锁。递归互斥锁可以被同一个线程多次加锁,以获得对互斥锁对象的多层所有权。例如,同一个线程多个函数访问临界区时都可以各自加锁,执行后各自解锁。std::recursive_mutex释放互斥量时需要调用与该锁层次深度相同次数的unlock(),即lock()次数和unlock()次数相同。可见,线程申请递归互斥锁时,如果该递归互斥锁已经被当前调用线程锁住,则不会产生死锁。此外,std::recursive_mutex的功能与 std::mutex大致相同。
- recursive_timed_mutex:定时递归 Mutex 类
用于互斥锁的RAII的类模板
[编辑]互斥类的最重要成员函数是lock() 和 unlock() 。在进入b:临界区时,执行lock()加锁操作,如果这时已经被其它线程锁住,则当前线程在此排队等待。退出临界区时,执行unlock()解锁操作。
更好的办法是采用“资源分配时初始化”(RAII)方法来加锁、解锁,这避免了在临界区中因为抛出异常或return等操作导致没有解锁就退出的问题。极大地简化了程序员编写Mutex相关的异常处理代码。
lock_guard类模板
[编辑]C++11的标准库中提供了lock_guard类模板做mutex的RAII,
template <class Mutex> class lock_guard;
模板参数Mutex是一个BasicLockable类型,标准库中定义的BasicLockable类型有:std::mutex, std::recursive_mutex, std::timed_mutex,std::recursive_timed_mutex 例如:
void foo( int n )
{
lock_guard<mutex> mLock( gMutex ); //gMutex是个全局变量
//mLock对象创建后,自动加锁
// Do something.......
//mLock对象生命期结束时,析构时自动解锁
}
- 成员函数
- lock_guard (mutex_type& m); 在构造时对互斥锁对象m进行上锁,通过调用 m.lock()
- lock_guard (mutex_type& m, adopt_lock_t tag); 构造函数“收养”(开始管理)已被当前线程加锁的Mutex对象,此后mutex对象在lock_guard对象析构时自动解锁
- lock_guard (const lock_guard&) = delete; 禁用拷贝构造,且禁用移动构造。
unique_lock类模板
[编辑]unique_lock 对象以独占所有权的方式( unique owership)管理 mutex 对象的上锁和解锁操作,即在unique_lock 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 unique_lock 的生命周期结束之后,它所管理的锁对象会被解锁。unique_lock具有lock_guard的所有功能,而且更为灵活。虽然二者的对象都不能复制,但是unique_lock可以移动(movable),因此用unique_lock管理互斥对象,可以作为函数的返回值,也可以放到STL的容器中。
unique_lock还支持同时锁定多个 mutex,这避免了多道加锁时的资源“死锁”问题。如下例所示:
// don't actually take the locks yet
std::unique_lock<std::mutex> lock1(mutex1, std::defer_lock);
std::unique_lock<std::mutex> lock2(mutex2, std::defer_lock);
// lock both unique_locks without deadlock
std::lock(lock1, lock2);
// Do something......
lock1.unlock();
lock2.unlock();
上例中,为了RAII的目的,在同时对两个锁加锁之后,还可以追加两条语句:
lock_guard<mutex> lock1( mutex1, adopt_lock );
lock_guard<mutex> lock2( mutex2, adopt_lock );
其中的adopt_lock常量参数是表示该锁已经成功获得。这样当程序退出临界区时,不用显式执行解锁操作,而是由两个lock_guard在析构时自动解锁。
- 构造函数
- unique_lock() noexcept; 默认构造函数,不管理任何 Mutex 对象。
- explicit unique_lock(mutex_type& m); 管理Mutex对象m,并调用m.lock()进行上锁,可被阻塞在互斥锁上
- unique_lock(mutex_type& m, try_to_lock_t tag);如果上锁不成功,并不会阻塞当前线程。
- unique_lock(mutex_type& m, defer_lock_t tag) noexcept;构造函数并不对Mutex对象申请上所
- unique_lock(mutex_type& m, adopt_lock_t tag);“收养”管理一个已经上锁的互斥锁对象
- template <class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time);通过调用 m.try_lock_for(rel_time)获取互斥锁
- template <class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);通过调用 m.try_lock_until(abs_time)获取互斥锁
- unique_lock(const unique_lock&) = delete;禁止拷贝构造
- unique_lock(unique_lock&& x);允许移动构造
- 运算符
- unique_lock& operator= (unique_lock&& x) noexcept;允许移动赋值
- unique_lock& operator= (const unique_lock&) = delete;禁止拷贝赋值
- operator bool():返回当前 std::unique_lock 对象是否获得了锁
- 成员函数
- lock():调用所管理的 Mutex 对象的 lock 函数
- try_lock():调用所管理的 Mutex 对象的 try_lock 函数
- try_lock_for():调用所管理的 Mutex 对象的 try_lock_for 函数
- try_lock_until():调用所管理的 Mutex 对象的 try_lock_until 函数
- unlock():调用所管理的 Mutex 对象的 unlock 函数
- release():返回所管理的 Mutex 对象的指针,并释放所有权。但不改变Mutex对象的状态
- owns_lock():返回当前 std::unique_lock 对象是否获得了锁
- mutex():返回当前 std::unique_lock 对象所管理的 Mutex 对象的指针
其他数据类型
[编辑]- std::once_flag
- std::adopt_lock_t:一个空的标记类。通常作为参数传入给 unique_lock 或 lock_guard 的构造函数,用于“收养”一个早已被加锁的mutex对象。
- std::defer_lock_t:一个空的标记类。该类型的常量对象defer_lock通常作为参数传入给 unique_lock 或 lock_guard 的构造函数。
- std::try_to_lock_t:一个空的标记类。该类型的常量对象try_to_lock通常作为参数传入给 unique_lock 或 lock_guard 的构造函数。
函数
[编辑]- std::try_lock():尝试同时对多个互斥量上锁。
- std::lock():可以同时对多个互斥量上锁。
- std::call_once():如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。
例子程序
[编辑]#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex gMutex;
void foo( int n )
{
gMutex.lock();
cout << "Something...."<<n<<endl;
gMutex.unlock();
}
int main( int argc, char** argv )
{
thread mThread1( foo, 3 );
thread mThread2( foo, 4 );
mThread1.join();
mThread2.join();
return 0;
}