C++11标准库 互斥锁 mutex 梳理

news/2024/11/15 6:33:14/文章来源:https://www.cnblogs.com/DSCL-ing/p/18302141

目录
  • <mutex>
    • std::call_once函数
      • 例程:使用call_once实现的单例模式
    • std::mutex类 -- 独占互斥锁
      • 成员函数
    • std::recursive_mutex类 -- 递归互斥锁
      • 使用注意:
      • 描述:
    • std::timed_mutex类 -- 超时互斥锁
      • 描述:
      • 成员函数:
    • std::recursive_timed_mutex类
    • std::lock_guard模板类
      • 函数原型:
    • std::unique_lock模板类
      • 成员方法

<mutex>

std::call_once函数

多线程操作过程中,std::call_once()内部的回调函数只会被执行一次

在某些特定情况下,某些函数只能在多线程环境下调用一次,比如:要初始化某个对象,而这个对象只能被初始化一次,就可以使用std::call_once()来保证函数在多线程环境下只能被调用一次。使用call_once()的时候,需要一个once_flag作为call_once()的传入参数.

函数原型:

// 定义于头文件 <mutex>,属于std
template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
  • flag:once_flag类型的对象,要保证这个对象能够被多个线程同时访问到
  • f:回调函数,可以传递一个有名函数地址,也可以指定一个匿名函数
  • args:作为实参传递给回调函数

例程:使用call_once实现的单例模式

#include<iostream>
#include<thread>
#include<mutex>std::once_flag g_flag;class Singleton {
public:Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton&s) = delete;static Singleton* GetInstance() {std::call_once(g_flag,[](){ std::cout<<"do once:"<<std::this_thread::get_id()<<"\n"; _instance = new Singleton; }); //成员函数中lambda默认隐式捕获thisstd::cout<<std::this_thread::get_id()<<"\n";return _instance;}private:Singleton(){};static Singleton* _instance;
};
Singleton* Singleton::_instance = nullptr;int main() {std::thread t1(Singleton::GetInstance);std::thread t2(Singleton::GetInstance);std::thread t3(Singleton::GetInstance);t1.join();t2.join();t3.join();return 0 ;
}

image-20240628223855820

注意:call_once在执行时会阻塞其他线程

std::call_once通过内部互斥器和原子操作来确保只有一个线程能够进入初始化代码区域。这意味着当多个线程等待执行某个需初始化的函数时,第一个获得执行权的线程将会执行该函数,而其他线程则会等待。这种机制确保了初始化过程的线程安全性,避免了竞态条件的发生。同时,std::call_once还处理了异常情况,如果在函数执行过程中抛出异常,则call_once不会认为该函数已经成功执行。这样,后续的线程仍然可以尝试进入执行,从而保证函数能够成功被执行一次。

image-20240629104811181

补充:

call_once实现的过程中使用了lambda表达式,而其中lambda表达式并未指明捕获方式,一般来说未指明时lambda不会捕获任何的外部变量.

但是为什么在成员函数中lambda不指定捕获类型却能捕获到成员变量呢?因为lambda在成员函数中隐式地捕获了当前对象的this指针,因此能够访问到成员变量.

这种便利也带来了一定的风险,因为在没有显式捕获的情况下,lambda表达式对成员变量的修改可能会影响到对象的状态,这点需要程序员注意.

std::mutex类 -- 独占互斥锁

解决多线程数据混乱的方案就是进行线程同步,最常用的就是互斥锁.

C++11中一共提供了四种互斥锁:

  1. std::mutex:独占的互斥锁,不能递归使用
  2. std::timed_mutex:带超时的独占互斥锁,不能递归使用
  3. std::recursive_mutex:递归互斥锁,不带超时功能
  4. std::recursive_timed_mutex:带超时的递归互斥锁

互斥锁在有些资料中也被称之为互斥量.

这些锁类都是不可拷贝,不可赋值

成员函数

  1. void lock();

    常用,阻塞上锁

  2. bool try_lock();

    try_lock,尝试上锁,线程不阻塞,返回值能够用于程序逻辑分支,即遇到锁后可以做其他事.

    例程:

    #include<iostream>
    #include<thread>
    #include<cstdlib>
    #include<mutex>int g_val;
    std::mutex mtx;void other() {std::cout << "do other thing" << "\n";
    }void func() {while (true) {_sleep(100);while (mtx.try_lock() == false)other();std::cout << g_val++ << "\n";mtx.unlock();}
    }int main() {std::thread t1(func);std::thread t2(func);std::thread t3(func);
    }
    

    image-20240629124646492

  3. void unlock();

    解锁

    注意,必须要上了锁的对象才有资格解锁.其他对象解锁程序会奔溃(mutex对象内会记录上锁线程的id,解锁时会进行id判定.)

    image-20240629125133502

死锁:

  • 一个执行流连续上次上同一把锁.
  • 多个执行流互相等待对方解锁

std::recursive_mutex类 -- 递归互斥锁

递归,就是可以多次使用

递归互斥锁std::recursive_mutex允许同一线程多次获得互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题

使用注意:

  • 使用递归互斥锁的场景往往都是可以简化的,使用递归互斥锁很容易放纵复杂逻辑的产生,从而导致bug的产生
  • 递归互斥锁比独占互斥锁实现复杂,考虑更多,因此比非递归互斥锁效率要低一些。
  • 递归互斥锁虽然允许同一个线程多次获得同一个互斥锁的所有权,但最大次数并未具体说明,一旦超过一定的次数,就会抛出std::system错误。

描述:

递归互斥锁,即同一个线程可以多次对该锁进行加锁操作.

形象来看,就是递归互斥锁就是一扇门,线程是主人,一扇门对应一把钥匙,主人能带着钥匙离开,能够使用钥匙重复开门.具有记忆功能

而独占互斥锁没有记忆功能,不能带走钥匙,用完离开后锁就不认人了.

std::timed_mutex类 -- 超时互斥锁

std::timed_mutex是超时独占互斥锁,体现在获取互斥锁资源时增加了两个超时等待的函数.

因为不知道获取锁资源需要等待多长时间,为了保证不一直等待下去,设置了一个超时时长,超时后线程就可以解除阻塞去做其他事情了。

描述:

和mutex的try_lock一样,只是try_lock不阻塞,try_lock_for会阻塞一定时长.

成员函数:

void lock();
bool try_lock();
void unlock();// std::timed_mutex比std::_mutex多出的两个成员函数
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);
  • try_lock_for函数是当线程获取不到互斥锁资源的时候,让线程阻塞一定的时间长度
  • try_lock_until函数是当线程获取不到互斥锁资源的时候,让线程阻塞到某一个指定的时间点

try_lock_for/until的返回值:

当得到互斥锁的所有权之后,函数会马上解除阻塞,返回true,如果阻塞的时长用完或者到达指定的时间点之后,函数也会解除阻塞,返回false

std::recursive_timed_mutex类

关于递归超时互斥锁std::recursive_timed_mutex的使用方式和std::timed_mutex一样,也是5个成员函数,只不过它可以允许一个线程多次获得互斥锁所有权,而std::timed_mutex只允许线程获取一次互斥锁所有权。

另外,递归超时互斥锁std::recursive_timed_mutex也拥有和std::recursive_mutex一样的弊端,不建议频繁使用。

std::lock_guard模板类

RAII技术,守护锁.

函数原型:

// 类的定义,定义于头文件 <mutex>
template< class Mutex >
class lock_guard;// 常用构造函数
explicit lock_guard( mutex_type& m );lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;

std::unique_lock模板类

前面讲到lock_guard能够RAII,但是仅限于简单的加锁解锁场景,因为lock_guard本身是不支持拷贝的,如果需要函数传参,返回等情况下,lock_guard就无能为力了.

unique_lock就是为此而实现,一般与条件变量搭配使用.

成员方法

使用方法与mutex基本一样

1. lock()
2. try_lock()
3. try_lock_for()	
4. try_lock_until()
5. unlock()	

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/743939.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【译】The danger of TaskCompletionSource class

来自 Sergey Tepliakov的另一篇 https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/#comments当使用async/await时,如果您想手动控制任务的生存期,TaskCompletionSource<T>类是一个非常有用的工具。下面是TaskCompletionSo…

Windows版PostgreSQL数据库下载及安装教程(关系型数据库管理系统)

前言 PostgreSQL是一个功能非常强大的、源代码开放的客户/服务器关系型数据库管理系统(RDBMS)。PostgreSQL最初设想于1986年,当时被叫做Berkley Postgres Project。该项目一直到1994年都处于演进和修改中,直到开发人员Andrew Yu和Jolly Chen在Postgres中添加了一个SQL(Str…

异构数据源数据同步 → 从源码分析 DataX 敏感信息的加解密

开心一刻 出门扔垃圾,看到一大爷摔地上了 过去问大爷:我账户余额 0.8,能扶你起来不 大爷往旁边挪了挪 跟我说到:孩子,快,你也躺下,这个来钱快! 我没理大爷,径直去扔了垃圾 然后飞速的躺在了大爷旁边,说道:感觉大爷带飞!书接上回 通过 异构数据源同步之数据同步 → …

开源工作流设计器(流程设计器)哪个好

本文重点介绍,基于activiti、flowable、camunda开源工作流引擎,如何选择一个开源免费的流程设计器,进行集成和扩展开发,快速交付项目使用。大家在开发OA办公自动化、ERP、CRM、BPM、低代码平台等项目的时候,经常用到流程引擎,目前主流的开源流程引擎有activiti、flowable…

如何设计应用系统的数据权限管理

在开发应用系统时,都离不开权限的设计,权限设计 = 功能权限 + 数据权限。而功能权限,在业界常常是基于RBAC(Role-Based Access Control)的一套方案。而数据权限,则根据不同的业务场景,则权限设计不尽相同,可以有不同的技术解决方案。按照应用系统权限类型划分,可进一步…

【进阶篇】一文搞清楚网页发起 HTTP 请求调用的完整过程

最近笔者在实际项目开发中会频繁涉及到服务之间的远程调用、域名的配置和请求的转发等与计算机网络相关的知识。这些其实在读本科和考研的时候都有学习过理论,但为了更透彻地掌握便于在工作中使用,我还是决定写一篇文章来分享实际开发中是怎么应用的。目录前言一、HTTP协议1.…

C++11标准库 原子变量 atomic 梳理

目录<atomic>原子操作的概念CAS实现原理CAS操作的伪代码:使用CAS完成变量的原子操作:CAS 操作的保证lock和锁的概念atomic模板类构造函数公共成员函数:atomic与互斥锁的效率比对 <atomic> C++11提供了一个原子类型std::atomic,通过这个原子类型管理的内部变量就可…

使用ML.NET训练一个属于自己的图像分类模型,对图像进行分类就这么简单!

前言 今天大姚给大家分享一个.NET开源、免费、跨平台(支持Windows、Linux、macOS多个操作系统)的机器学习框架:ML.NET。并且本文将会带你快速使用ML.NET训练一个属于自己的图像分类模型,对图像进行分类。ML.NET框架介绍 ML.NET 允许开发人员在其 .NET 应用程序中轻松构建、…

网页交互

单击 选择元素 (html)<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title&g…

读人工智能全传13人工智能导致的问题2

读人工智能全传13人工智能导致的问题21. 机器人sha手 1.1. 自主57的话题总是带有强烈的煽动性,许多人会本能地厌恶它,认为这样的系统是不道德的,永远不该被建立 1.2. 自主57的讨论大多源于战争中使用得越来越频繁的无人机 1.3. 无人机 1.3.1. 人驾驶的飞机,在菌用领域,它可…

万字:清结算体系,全局方案深度解析

本文分享了头部支付机构是如何做清结算的,在做和带领大家打通支付的底层处理原理,内核中的内核,分享给大家。支付机构帮助交易平台代收代付交易款,那么就需要先从消费者发卡行把钱拿过来,然后再结算给交易平台;对于交易平台也是一样的道理,要帮店家卖东西,需要帮忙通过…

【THM】tomghost练习

先努力成为脚本小子【THM】tomghost练习 与本文相关的TryHackMe实验房间链接:TryHackMe | Room details 简介:识别最近的漏洞,以尝试利用系统或读取你没有权限访问的文件。 **你能完成这个挑战吗? **机器可能需要长达5分钟的启动和配置。 管理员记录:这个房间的用户名包含不…