简介
在多线程中往往需要访问临界资源,C++11为我们提供了mutex等相关类来保护临界资源,保证某一时刻只有一个线程可以访问临界资源。主要包括各种mutex,他们的命名大都是xx_mutex。以及RAII风格的wrapper类,RAII就是一般在构造的时候上锁,在析构的时候解锁。
C++11提供的锁类型有三个:
- mutex,头文件:
- timed_mutex,头文件:
- recursive_mutex,头文件:
- recursive_timed_mutex,头文件:<shared_mutex>
C++11提供的RAII风格的wrapper类有两个:
- lock_guard,头文件:
- unique_lock,头文件:
std::mutex
mutex提供的方法不过,主要有lock和unlock
mutex只有默认构造方法,不允许拷贝构造,目前也没有提供移动构造
cpp
constexpr mutex() noexcept;
mutex( const mutex& ) = delete;
lock和try_lock的区别是,lock会阻塞当前线程,而try_lock不会,如果没有获取到锁,则返回false,如果获取到则返回true。
std::timed_mutex
相比于mutex,timed_mutex多了try_lock_for和try_lock_until两个方法。try_lock_for表示花多长时间尝试获取锁,如果超过时长则失败。try_lock_until表示尝试获取锁到什么时候,如果超过指定时间点则失败。
try_lock_for的函数声明如下,两个模板参数都是形参timeout_duration的,Rep表示保存时间段的类型,Period表示单位,比如秒,毫秒等。详细参考:chrono。try_lock_for表达的意思就是:阻塞获取锁,最长阻塞timeout_duration时间。如果期间获取到了锁,则返回true,否则返回false。如果timeout_duration小于等于0,try_lock_for的行为就和try_lock一样。
cpp
template< class Rep, class Period >
bool try_lock_for( const std::chrono::duration<Rep, Period>& timeout_duration );
例子:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
void run(std::timed_mutex &mutex)
{
if (mutex.try_lock_for(std::chrono::milliseconds(500))) {
std::cout << "获得了锁" << std::endl;
} else {
std::cout << "未获得锁" << std::endl;
}
}
int main() {
std::timed_mutex mutex;
mutex.lock();
std::thread thread(run, std::ref(mutex));
thread.join();
mutex.unlock();
return 0;
}
//输出:未获得锁
try_lock_until的函数声明如下,两个模板参数都是形参timeout_time的,Clock是始终类型,Duration就是前面的std::chrono::duration。try_lock_until表达的意识就是:阻塞获取锁,一直阻塞到timeout_time这个时间点。如果期间获取到了锁,则返回true,否则返回false。time_point 参考。
cpp
template< class Clock, class Duration >
bool try_lock_until( const std::chrono::time_point<Clock, Duration>& timeout_time );
例子:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
void run(std::timed_mutex &mutex)
{
std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
if (mutex.try_lock_for(now + std::chrono::milliseconds(500))) {
std::cout << "获得了锁" << std::endl;
} else {
std::cout << "未获得锁" << std::endl;
}
}
int main() {
std::timed_mutex mutex;
mutex.lock();
std::thread thread(run, std::ref(mutex));
thread.join();
mutex.unlock();
return 0;
}
//输出:未获得锁
std::recursive_mutex
recursive_mutex和mutex的区别就在"recursive",recursive_mutex允许同一个线程多次lock,当然需要相同次数的unlock。c++没有规定最多可以调用多少次,如果到达了最大lock次数,lock方法会抛出异常(std::system_error),try_lock会返回false。
std::recursive_timed_mutex
recursive_timed_mutex就是recursive_mutex和timed_mutex的结合体,提供的方法如下:
std::lock_guard
lock_guard是一个RAII风格mutex wrapper,即就是在他析构的时候,会解锁他关联的mutex。一般在构造lock_guard的时候,给mutex上锁,当然也有例外,具体得看在调用lock_guard的构造方法时传的参数。
下面是lock_guard提供的方法
构造方法可用的有两个
cpp
explicit lock_guard( mutex_type& m ); // 在构造lock_guard的时候调用m的lock方法,在析构的时候调用m的unlock方法
lock_guard( mutex_type& m, std::adopt_lock_t t ); // 只是关联m,但是不调用m的lock方法,在析构的时候调用m的unlock方法
lock_guard( const lock_guard& ) = delete; // 禁止拷贝构造lock_guard
std::unique_lock
相比于lock_guard,unique_lock提供了更强大的功能,虽然不能拷贝,但是可以移动。处理支持mutex的所有操作外,还可以支持mutex延迟上锁,尝试上锁等。lock_guard提供的方法有:
unique_lock提供的构造方法比较多:
cpp
unique_lock() noexcept; // 构造一个不关联mutex的unique_lock对象,他可以通过移动拷贝操作符关联到一个mutex,或者调用swap方法
unique_lock( unique_lock&& other ) noexcept; // 移动构造
explicit unique_lock( mutex_type& m ); // 构造unique_lock的时候,调用m.lock()
unique_lock( mutex_type& m, std::defer_lock_t t ) noexcept; // 仅关联m,但是不调用m的lock方法
// 关联m,并且调用m的try_lock方法,当然前提是m有try_lock方法,如果没有,则行为是未定义的。
// try_lock可能失败,返回false。unique_lock的构造方法没有返回值,我们怎么知道m有没有上锁成功。
// 调用unique_lock的owns_lock方法,他返回bool,可以知道m有没有上锁成功。具体可以参考例子
unique_lock( mutex_type& m, std::try_to_lock_t t );
// 关联已经上锁的m,如果m没有上锁,则行为未定义
unique_lock( mutex_type& m, std::adopt_lock_t t );
// 构造unique_lock的时候,关联m,并且执行m.try_lock_for(timeout_duration)。
// m上锁有没有成功,仍然可以通过unique_lock的owns_lock方法获取
template< class Rep, class Period >
unique_lock( mutex_type& m, const std::chrono::duration<Rep, Period>& timeout_duration );
// 和上一个方法类似,只不过执行的是m.try_lock_until(timeout_time)
template< class Clock, class Duration >
unique_lock( mutex_type& m, const std::chrono::time_point<Clock, Duration>& timeout_time );
try_to_lock的例子:
cpp
#include <iostream>
#include <mutex>
std::mutex mtx;
void fun() {
std::unique_lock<std::mutex> guard(mtx, std::try_to_lock);
if (m_guard1.owns_lock()) {
std::cout << "try_to_lock success" << std::endl;
} else {
std::cout << "try_to_lock failed" << std::endl;
}
}
当unique_lock对象成功关联到了mutex,并且他获得了锁,则在析构的时候调用mutex的unlock方法。
unique_lock仅支持移动拷贝赋值操作符
cpp
unique_lock& operator=( unique_lock&& other );
unique_lock还提供了获取mutex的方法,在调用unique_lock提供的各种lock类方法时,就如同mutex()->lock()
cpp
mutex_type* mutex() const noexcept;
unique_lock还提供了unlock方法,当调用这个方法的时候,会调用mutex的unlock方法,并且unique_lock释放mutex,即不再关联当前mutex。当时当调用unlock方法时,没有关联到mutex,或者mutex没有获得锁,则会抛出std::system_error异常。
cpp
void unlock();
unique_lock的release方法只是与关联的mutex断开关联,并不会调用mutex的unlock方法
cpp
mutex_type* release() noexcept;
bool操作符,相当于调用owns_lock(),具体使用参考如下实例:
cpp
explicit operator bool() const noexcept;
cpp
#include <iostream>
#include <mutex>
#include <thread>
class Test {
public:
void fun() {
std::unique_lock<std::mutex> lck(m_mtx);
if (bool(lck)) {
std::cout << "lock succ: bool(lck)" << std::endl;
}
if ((bool)lck) {
std::cout << "lock succ: (bool)lck" << std::endl;
}
if (lck) {
std::cout << "lock succ: lck" << std::endl;
}
}
private:
std::mutex m_mtx;
};
int main() {
Test test;
std::thread thread(&Test::fun, &test);
thread.join();
return 0;
}
/** 输出结果
* lock succ: bool(lck)
* lock succ: (bool)lck
* lock succ: lck
*/