c++之mutex_c++ mutex-程序员宅基地

技术标签: c++11  工作  

前言

mutex又称互斥量,用于提供对共享变量的互斥访问。C++11中mutex相关的类都在头文件中。
共四种互斥类:

序号 名称 用途
1 std::mutex 最基本也是最常用的互斥类
2 std::recursive_mutex 同一线程内可递归(重入)的互斥类
3 std::timed_mutex 除具备mutex功能外,还提供了带时限请求锁定的能力
4 std::recursive_timed_mutex 同一线程内可递归(重入)的timed_mutex

mutex相关类不支持拷贝构造、不支持赋值。同时mutex类也不支持move语义(move构造、move赋值)。不用担心会误用这些操作,真要这么做了的话,编译器会阻止你的。

头文件:

#include <<mutex>

操作

lock, try_lock, unlock

mutex的标准操作,四个mutex类都支持这些操作,但是不同类在行为上有些微的差异。

lock

锁住互斥量。调用lock时有三种情况:

  • 如果互斥量没有被锁住,则调用线程将该mutex锁住,直到调用线程调用unlock释放。
  • 如果mutex已被其它线程lock,则调用线程将被阻塞,直到其它线程unlock该mutex。
  • 如果当前mutex已经被调用者线程锁住,则std::mutex死锁,而recursive系列则成功返回。

try_lock

尝试锁住mutex,调用该函数同样也有三种情况:

  • 如果互斥量没有被锁住,则调用线程将该mutex锁住(返回true),直到调用线程调用unlock释放。
  • 如果mutex已被其它线程lock,则调用线程将失败,并返回false。
  • 如果当前mutex已经被调用者线程锁住,则std::mutex死锁,而recursive系列则成功返回true。

unlock

解锁mutex,释放对mutex的所有权。值得一提的时,对于recursive系列mutex,unlock次数需要与lock次数相同才可以完全解锁。
下面给出一个mutex小例子:

#include <iostream>
#include <thread>
#include <mutex>

void inc(std::mutex &mutex, int loop, int &counter) {
    
    for (int i = 0; i < loop; i++) {
    
        mutex.lock();
        ++counter;
        mutex.unlock();
    }
}
int main() {
    
    std::thread threads[5];
    std::mutex mutex;
    int counter = 0;

    for (std::thread &thr: threads) {
    
        thr = std::thread(inc, std::ref(mutex), 1000, std::ref(counter));
    }
    for (std::thread &thr: threads) {
    
        thr.join();
    }

    // 输出:5000,如果inc中调用的是try_lock,则此处可能会<5000
    std::cout << counter << std::endl;

    return 0;
}
//: g++ -std=c++11 main.cpp

try_lock_for, try_lock_until

这两个函数仅用于timed系列的mutex(std::timed_mutex, std::recursive_timed_mutex),函数最多会等待指定的时间,如果仍未获得锁,则返回false。除超时设定外,这两个函数与try_lock行为一致。

// 等待指定时长
template <class Rep, class Period>
    try_lock_for(const chrono::duration<Rep, Period>& rel_time);
// 等待到指定时间
template <class Clock, class Duration>
    try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);

try_lock_for相关代码:

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

void run500ms(std::timed_mutex &mutex) {
    
    auto _500ms = std::chrono::milliseconds(500);
    if (mutex.try_lock_for(_500ms)) {
    
        std::cout << "获得了锁" << std::endl;
    } else {
    
        std::cout << "未获得锁" << std::endl;
    }
}
int main() {
    
    std::timed_mutex mutex;

    mutex.lock();
    std::thread thread(run500ms, std::ref(mutex));
    thread.join();
    mutex.unlock();

    return 0;
}
//输出:未获得锁

lock_guard, unique_lock,std::call_once, std::try_lock, std::lock(批量上锁)

lock_guard

lock_guard利用了C++ RAII的特性,在构造函数中上锁,析构函数中解锁。lock_guard是一个模板类,其原型为

template <class Mutex> class lock_guard

模板参数Mutex代表互斥量,可以是上一篇介绍的std::mutex, std::timed_mutex, std::recursive_mutex, std::recursive_timed_mutex中的任何一个,也可以是std::unique_lock(下面即将介绍),这些都提供了lock和unlock的能力。
lock_guard仅用于上锁、解锁,不对mutex承担供任何生周期的管理,因此在使用的时候,请确保lock_guard管理的mutex一直有效。
同其它mutex类一样,locak_guard不允许拷贝,即拷贝构造和赋值函数被声明为delete。

lock_guard(lock_guard const&) = delete;
lock_guard& operator=(lock_guard const&) = delete;

lock_guard的设计保证了即使程序在锁定期间发生了异常,也会安全的释放锁,不会发生死锁。

#include <iostream>
#include <mutex>

std::mutex mutex;

void safe_thread() {
    
    try {
    
        std::lock_guard<std::mutex> _guard(mutex);
        throw std::logic_error("logic error");
    } catch (std::exception &ex) {
    
        std::cerr << "[caught] " << ex.what() << std::endl;
    }
}
int main() {
    
    safe_thread();
    // 此处仍能上锁
    mutex.lock();
    std::cout << "OK, still locked" << std::endl;
    mutex.unlock();

    return 0;
}

unique_lock

lock_guard提供了简单上锁、解锁操作,但当我们需要更灵活的操作时便无能为力了。这些就需要unique_lock上场了。unique_lock拥有对Mutex的所有权,一但初始化了unique_lock,其就接管了该mutex, 在unique_lock结束生命周期前(析构前),其它地方就不要再直接使用该mutex了。unique_lock提供的功能较多,此处不一一列举,下面列出unique_lock的类声明,及部分注释,供大家参考

template <class Mutex>
class unique_lock
{
    
public:
    typedef Mutex mutex_type;
    // 空unique_lock对象
    unique_lock() noexcept;
    // 管理m, 并调用m.lock进行上锁,如果m已被其它线程锁定,由该构造了函数会阻塞。
    explicit unique_lock(mutex_type& m);
    // 仅管理m,构造函数中不对m上锁。可以在初始化后调用lock, try_lock, try_lock_xxx系列进行上锁。
    unique_lock(mutex_type& m, defer_lock_t) noexcept;
    // 管理m, 并调用m.try_lock,上锁不成功不会阻塞当前线程
    unique_lock(mutex_type& m, try_to_lock_t);
    // 管理m, 该函数假设m已经被当前线程锁定,不再尝试上锁。
    unique_lock(mutex_type& m, adopt_lock_t);
    // 管理m, 并调用m.try_lock_unitil函数进行加锁
    template <class Clock, class Duration>
        unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time);
    // 管理m,并调用m.try_lock_for函数进行加锁
    template <class Rep, class Period>
        unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time);
    // 析构,如果此前成功加锁(或通过adopt_lock_t进行构造),并且对mutex拥有所有权,则解锁mutex
    ~unique_lock();

    // 禁止拷贝操作
    unique_lock(unique_lock const&) = delete;
    unique_lock& operator=(unique_lock const&) = delete;

    // 支持move语义
    unique_lock(unique_lock&& u) noexcept;
    unique_lock& operator=(unique_lock&& u) noexcept;

    void lock();
    bool try_lock();

    template <class Rep, class Period>
        bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);

    // 显示式解锁,该函数调用后,除非再次调用lock系列函数进行上锁,否则析构中不再进行解锁
    void unlock();

    // 与另一个unique_lock交换所有权
    void swap(unique_lock& u) noexcept;
    // 返回当前管理的mutex对象的指针,并释放所有权
    mutex_type* release() noexcept;

    // 当前实例是否获得了锁
    bool owns_lock() const noexcept;
    // 同owns_lock
    explicit operator bool () const noexcept;
    // 返回mutex指针,便于开发人员进行更灵活的操作
    // 注意:此时mutex的所有权仍归unique_lock所有,因此不要对mutex进行加锁、解锁操作
    mutex_type* mutex() const noexcept;
};

std::call_once

该函数的作用顾名思义:保证call_once调用的函数只被执行一次。该函数需要与std::once_flag配合使用。std::once_flag被设计为对外封闭的,即外部没有任何渠道可以改变once_flag的值,仅可以通过std::call_once函数修改。一般情况下我们在自己实现call_once效果时,往往使用一个全局变量,以及双重检查锁(DCL)来实现,即便这样该实现仍然会有很多坑(多核环境下)。有兴趣的读者可以搜索一下DCL来看,此处不再赘述。
C++11为我们提供了简便的解决方案,所需做的仅仅像下面这样使用即可。

#include <iostream>
#include <thread>
#include <mutex>

void initialize() {
    
    std::cout << __FUNCTION__ << std::endl;
}

std::once_flag of;
void my_thread() {
    
    std::call_once(of, initialize);
}

int main() {
    
    std::thread threads[10];
    for (std::thread &thr: threads) {
    
        thr = std::thread(my_thread);
    }
    for (std::thread &thr: threads) {
    
        thr.join();
    }
    return 0;
}
// 仅输出一次:initialize

std::try_lock

当有多个mutex需要执行try_lock时,该函数提供了简便的操作。try_lock会按参数从左到右的顺序,对mutex顺次执行try_lock操作。当其中某个mutex.try_lock失败(返回false或抛出异常)时,已成功锁定的mutex都将被解锁。
需要注意的是,该函数成功时返回-1, 否则返回失败mutex的索引,索引从0开始计数。

template <class L1, class L2, class... L3>
  int try_lock(L1&, L2&, L3&...);

std::lock

std::lock是较智能的上批量上锁方式,采用死锁算法来锁定给定的mutex列表,避免死锁。该函数对mutex列表的上锁顺序是不确定的。该函数保证: 如果成功,则所有mutex全部上锁,如果失败,则全部解锁。

template <class L1, class L2, class... L3>
  void lock(L1&, L2&, L3&...);
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39658118/article/details/103619379

智能推荐

浅谈计算机辅助数学教学论文,论文浅谈对计算机辅助数学教学的认识.doc-程序员宅基地

文章浏览阅读201次。论文浅谈对计算机辅助数学教学的认识.docXx师范学院本科生毕业论文(设计)题 目专 业院 部学 号姓 名指 导 教 师答 辩 时 间论文工作时间: 年 月 至 年 月摘 要:计算机辅助教学(CAI)在小学数学教学中,有着广泛的应用,它可以解决很多教学问题,所以在教学中的优越性不可忽视。但是在实际运..._浅谈计算机辅助数学教学

windows xshell6启动时msvcp110.dll、msvcr110.dll、mfc110u.dll丢失解决_xshell6运行报错:由于找不到mfc110u.dll、msvcr110.dll无-程序员宅基地

文章浏览阅读7.3k次,点赞6次,收藏13次。经过重重的磨难,终于解决了,真是踩了好多坑。启动xshell时程序报错如下: 无法启动此程序,因为计算机中丢失MSVCR110.dll。尝试重新安装该程序以解决此问题。尝试了好多种办法:1、百度下载修复工具失败。2、下载360安全卫士进行dll修复,解决了一个。3、又出现了找不到的dll安装Visual C++ Redistributable for Visual Studio 2..._xshell6运行报错:由于找不到mfc110u.dll、msvcr110.dll无

机器学习找工作,学历到底多重要?业内顶尖开发者持这个态度!-程序员宅基地

文章浏览阅读403次。 半路出家的机器学习求职者,有出路么?了解一点机器学习的同学都知道,人工智能领域是非常开放的,国内外很多的资源、教程、数据集等等都可以免费获取到。在这里给大家推荐一个python系统学习q群:250933691有免费开发工具以及初学资料,(数据分析,爬虫,机器学习,神经网络)每天有老师给大家免费授课,欢迎一起交流学习这种优势不仅促进了该领域的极速发展,也使得越来越多的人能够通过...

HTML中的背景设置(上)_html background-color-程序员宅基地

文章浏览阅读3.1k次,点赞4次,收藏12次。HTML中的背景设置(上)_html background-color

36.(leaflet之家)leaflet+turf截取线上线段_leaflet加turf生成等值线图-程序员宅基地

文章浏览阅读173次。听老人家说:多看美女会长寿 leaflet之家总目录(订阅之前建议先查看该博客)文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。效果如下所示:下面献上完整代码,代码重要位置会做相应解释<!DOCTYPE html><html><head> <meta charset=utf-8 /> <title>Historic Topographic Maps</title> <meta nam_leaflet加turf生成等值线图

java 怎么保证余额_高并发下怎么做余额扣减?-程序员宅基地

文章浏览阅读2.8k次。余额操作在大多数系统都是不可缺少和不允许出现问题的 如何修改余额 , 这个问题可能在实际项目中 没那么简单;如何修改余额假设一个用户数据 :id⇒12 | user_name⇒放放 | fee⇒ 30 | updated_at ⇒ 2019-09-06 15:51:33修改余额//消费金额$spend = 10;//查询用户余额$user = select id,fee from `..._java下单检查余额事务

随便推点

STM32入门(十五)----SysTick系统定时器_实验十五 systick-系统定时器-程序员宅基地

文章浏览阅读497次。SysTick系统定时器SysTick简介SysTick框图SysTick定时实验程序SysTick简介SysTick:系统定时器,24位,只能递减,存在于内核,嵌套在NVIC中,所有的Cortex-M内核的单片机都具有这个定时器。《STM32参考手册》里的一句话:关于Cortex-M3核心、 SysTick定时器和NVIC的详细说明,请参考另一篇ST的文档和一篇ARM的文档:《STM32F10xxx Cortex-M3编程手册》和《Cortex-M3技术参考手册》。SysTick框图co_实验十五 systick-系统定时器

秦俊东北计算机博士,秦 俊 教授-云南大学省部共建教育部自然资源药物化学重点实验室...-程序员宅基地

文章浏览阅读434次。秦俊,博士研究方向:药物化学和有机化学学位:博士毕业学校:The University of Vermont, USA(美国佛蒙特大学化学系)职称:教授/博导电子邮件:[email protected]职业经历2013/07-现在:教授—云南大学化学科学与工程学院,昆明。2009/11-2013/06:副主任科学家—Merck Research Laboratories, New Jersey20..._云南大学秦俊

福建建材学校计算机专业学费,福建省民办学校的收费标准 2017年中小学收费标准一览...-程序员宅基地

文章浏览阅读547次。原标题:福建省民办学校的收费标准 2017年中小学收费标准一览福建省民办学校的收费标准,2017年中小学收费标准一览,今秋泉州公办中小学收费标准确定,公办学校免学费,具体内容详解请看下文。三所市直民办校收费标准出炉根据日前市物价局印发的有关文件,今年秋季泉州实验中学、泉州外国语中学、泉州市第三实验小学这三所市直民办校的收费标准已经确定。根据文件规定,今年秋季泉州实验中学、泉州外国语中学两所学校的学..._福州建材中专学费

Pygame基础知识(5)-颜色_pygame颜色数值-程序员宅基地

文章浏览阅读2.5k次,点赞8次,收藏7次。欢迎来到pygame大讲堂,这次我们将给大家介绍颜色。光线有三种主要颜色,分别是红色,绿色和蓝色,不同于美术中的三原色,所以不要搞混了。你可以将这些颜色按不同比例结合起来,组合出不同的颜色。在pygame中,每一种颜色的范围在0-255之间,我们可以使用一个包括三个整数的元组来表示一种颜色,第一个整数表示红色,第二个整数表示绿色,第三个整数表示蓝色,这就称为RGB值。如果你想表示颜色,你可以..._pygame颜色数值

《scikit-learn机器学习》 Python代码的埃拉托斯特尼筛法:找出一定范围内所有的素数_埃筛法求区间素数python-程序员宅基地

文章浏览阅读770次。埃拉托斯特尼筛法先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个素数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个素数5筛,把5留下,把5的倍数剔除掉;不断重复下去…import numpy as np a= np.arange(1, 101) n_max = int(np.sqrt(len(a))) is_prime = np.ones(len(a),dtype=bool) #创建 100 个元素的数组,用来 示记是否为质数is_prime[0] = False for i i_埃筛法求区间素数python

怎么拿到BERT任意层的embedding结果_transformers框架如何提取bert模型的embedding-程序员宅基地

文章浏览阅读3.1k次,点赞3次,收藏15次。本方法基于hugginface的transformers项目改造过程其实很简单,基于TFBertMainLayer下的call函数做了改造,如果直接用self.bert的输出其实就是CLS token 的结果。使用时同样可以使用from_pretrained来加载下载好的BERT模型参数,然后把输入的数据整理为input_ids, attention_mask,token_type_ids格式即可。使用tf.datasets也可。from transformers.modeling_t_transformers框架如何提取bert模型的embedding