C++/Mutex

维基教科书,自由的教学读本
< C++

mutexC++標準程式庫中的一個头文件,定义了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;
}

参考文献[编辑]