【C++11并发】mutex 笔记

简介

在多线程中往往需要访问临界资源,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
 */
相关推荐
jjjxxxhhh12321 分钟前
c++设计模式之策略模式
c++·设计模式·策略模式
白八实27 分钟前
汇编代码中的主要指令笔记
汇编·jvm·笔记
埋头编程~41 分钟前
【C++】踏上C++的学习之旅(六):深入“类和对象“世界,掌握编程的黄金法则(一)
开发语言·c++·学习
非概念43 分钟前
STM32学习笔记-----UART的概念
笔记·stm32·单片机·嵌入式硬件·学习
DisonTangor1 小时前
【个人笔记】如何将 Linux 文件系统扩容
linux·运维·笔记
嵌入式小小怪下士1 小时前
SRIO & RapidIO 笔记
笔记
清酒伴风(面试准备中......)2 小时前
计算机网络HTTP——针对实习面试
java·笔记·网络协议·计算机网络·http·面试·实习
如意.7592 小时前
【C++】—— map 与 set 深入浅出:设计原理与应用对比
开发语言·c++
起名字真南2 小时前
【C++】深入理解自定义 list 容器中的 list_iterator:迭代器实现详解
数据结构·c++·windows·list
蹊黎2 小时前
C++模版初阶
开发语言·c++