【并发编程九】c++线程同步——互斥(mutex)

news2024/9/24 1:16:36

【并发编程九】c++线程同步——互斥(mutex)

  • 一、互斥
    • 1、mutex
      • 1.1、mutex
      • 1.2、 lock_guard
      • 1.3、 RAII
    • 2、std::recursive_mutex
    • 3、std::shared_mutex、std::shared_lock、std::unique_lock
    • 4、std::scoped_lock
  • 二、条件变量
  • 三、future
  • 四、信号量

  • 简介:
    上一篇文章,我们讲解了windows、linux、c++标准库的线程和线程同步方法,【并发编程八】线程和线程同步。本篇文章,我们详细的介绍下c++标准库提供的线程同步方法。

  • 参考:
    1、https://www.apiref.com/cpp-zh/cpp/thread.html
    2、https://en.cppreference.com/w/cpp/thread
    3、书籍《c++服务器开发精髓》——张远龙

一、互斥

  • 互斥算法避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。
    前四个定义于头文件 <mutex>,后两个定义于头文件 <shared_mutex>
互斥类型解释
mutex(C++11)提供基本互斥设施(类)
timed_mutex(C++11)提供互斥设施,实现有时限锁定(类)
recursive_mutex(C++11)提供能被同一线程递归锁定的互斥设施(类)
recursive_timed_mutex(C++11)
提供能被同一线程递归锁定的互斥设施,并实现有时限锁定(类)
shared_mutex(C++17)提供共享互斥设施(类)
shared_timed_mutex(C++14)提供共享互斥设施并实现有时限锁定(类)
  • 通用互斥管理
    定义于头文件<mutex>
互斥管理解释
lock_guard(C++11)实现严格基于作用域的互斥体所有权包装器(类模板)
scoped_lock (C++17)用于多个互斥体的免死锁 RAII 封装器(类模板)
unique_lock (C++11)实现可移动的互斥体所有权包装器(类模板)
shared_lock(C++14)实现可移动的共享互斥体所有权封装器(类模板)
defer_lock_t(C++11)
try_to_lock_t(C++11)
adopt_lock_t(C++11)
用于指定锁定策略的标签类型(类)
defer_lock(C++11)
try_to_lock(C++11)
adopt_lock(C++11)
用于指定锁定策略的标签常量(常量)

1、mutex

1.1、mutex

  • mutex 类是能用于保护共享数据免受从多个线程同时访问的同步原语。
  • 通常不直接使用 std::mutex ,我们通常使用 std::unique_lock 、 std::lock_guard 或 std::scoped_lock (C++17 起)以更加异常安全的方式管理锁定。
  • 此示例展示 mutex 能如何用于在保护共享于二个线程间的 std::map 。
  • 如果不会map加锁,我们输出的map中的值时,可能只有一个值。

1.2、 lock_guard

  • 类 lock_guard 是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。
  • 创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁lock_guard 并释放互斥。
  • lock_guard 类不可复制。

1.3、 RAII

  • 资源获取即初始化(Resource Acquisition Is Initialization),或称 RAII,是一种 C++ 编程技术,它将必须在使用前请求的资源(分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事物)的生命周期绑定与一个对象的生存期相绑定。
#include <iostream>
#include <map>
#include <string>
#include <chrono>
#include <thread>
#include <mutex>

std::map<std::string, std::string> g_pages;
std::mutex g_pages_mutex;

void save_page(const std::string& url)
{
    // 模拟长页面读取
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::string result = "fake content";

    std::lock_guard<std::mutex> guard(g_pages_mutex);
    g_pages[url] = result;
}

int main()
{
    std::thread t1(save_page, "http://foo");
    std::thread t2(save_page, "http://bar");
    t1.join();
    t2.join();

    // 现在访问g_pages是安全的,因为线程t1/t2生命周期已结束
    for (const auto& pair : g_pages) {
        std::cout << pair.first << " => " << pair.second << '\n';
    }
}

在这里插入图片描述

2、std::recursive_mutex

  • recursive_mutex 类是同步原语,能用于保护共享数据免受从个多线程同时访问。
  • recursive_mutex 提供排他性递归所有权语义:
  • recursive_mutex 的使用场景之一是保护类中的共享状态,而类的成员函数可能相互调用
  • 用法:
    recursive_mutex的用处和mutex差不多,用于限制多线程同时访问同一个变量,用来加锁,保证多个线程,同一时刻只能有一个线程在修改变量;和mutex不同的时,recursive_mutex可以允许同一个线程递归的去加锁,线程只有加锁次数和释放次数相同时,才会释放变量的控制权;例如下面的fun2中调用了fun1,但是fun1和fun2中都加了锁,如果使用mutex,在fun1加锁,在fun2中再次加锁,就会造成死锁;所以recursive_mutex可以避免递归嵌套调用时,造成的死锁问题;递归调用不会死锁,同一线程使用recursive_mutex加锁次数和解锁次数相等时释放控制权;
#include <iostream>
#include <thread>
#include <mutex>

class X {
    std::recursive_mutex m;
    std::string shared;
public:
    void fun1() {
        std::lock_guard<std::recursive_mutex> lk(m);
        shared = "fun1";
        std::cout << "in fun1, shared variable is now " << shared << '\n';
    }
    void fun2() {
        std::lock_guard<std::recursive_mutex> lk(m);
        shared = "fun2";
        std::cout << "in fun2, shared variable is now " << shared << '\n';
        fun1(); // 递归锁在此处变得有用
        std::cout << "back in fun2, shared variable is " << shared << '\n';
    };
};

int main()
{
    X x;
    std::thread t1(&X::fun1, &x);
    std::thread t2(&X::fun2, &x);
    t1.join();
    t2.join();
}

在这里插入图片描述

3、std::shared_mutex、std::shared_lock、std::unique_lock

C++17 std::shared_mutex的替代方案boost::shared_mutex
shared_mutex 类,结合 unique_lock 与 shared_lock 的使用,可以实现读写锁。

通常读写锁需要完成以下功能:

  • 1.当 data 被线程A读取时,其他线程仍可以进行读取却不能写入
  • 2.当 data 被线程A写入时,其他线程既不能读取也不能写入

对应于功能1,2我们可以这样来描述:

  • 1.当线程A获得共享锁时,其他线程仍可以获得共享锁但不能获得独占锁
  • 2.当线程A获得独占锁时,其他线程既不能获得共享锁也不能获得独占锁
#include<iostream>
#include <thread>
#include<shared_mutex>
using namespace std;

typedef std::shared_lock<std::shared_mutex> read_lock;
typedef std::unique_lock<std::shared_mutex> write_lock;

std::shared_mutex read_write_mutex;
int32_t g_data =0;

//线程A,读data
void fun1()
{
    for (size_t i = 0; i < 10; i++)
    {
        read_lock rlock(read_write_mutex);
        cout << "t1:g_data=" << g_data << endl;
    }
}

//线程B,读data
void fun2()
{
    for (size_t i = 0; i < 10; i++)
    {
        read_lock rlock(read_write_mutex);
        std::cout <<"t2:g_data="<< g_data << endl;
    }
}

//线程C,写data
void fun3()
{
    for (size_t i = 0; i < 10; i++)
    {
        write_lock rlock(read_write_mutex);
        g_data++;
        std::cout << "t3:g_data=" << g_data << endl;
    }
}

int main()
{
    thread t3(fun3);
    thread t1(fun1);
    thread t2(fun2);
    
 
    t1.join();
    t2.join();
    t3.join();
}

输出如下
在这里插入图片描述

结果说明:

  • 线程1和线程2可以同时读,
    (由于io是进程间共享的,可以看到线程1和2同时操作时,在换行endl还没有输出完,两个线程出现了抢占io资源,所以,我们看到了t1和t2输出到了同一行)
  • 在线程1或者线程2加了锁读时,我们的线程3不可以写的。(所以,我们看不到t3和t1或者t2在同一行)
  • 线程3写入时,不允许线程1和线程3写入。(所以,我们看不到t3和t1或者t2在同一行)

简单总结下

  • std::shared_lock是读锁,被锁后仍允许其他线程执行同样被shared_lock的代码,
    当data被线程A读取时,仍允许其它线程读取data,但是不能写入。
  • std::unique_lock是写锁。被锁后不允许其他线程执行被shared_lock或unique_lock的代码。
    在写操作时,一般用这个,可以同时限制unique_lock的写和share_lock的读。

4、std::scoped_lock

lock_guard:更加灵活的锁管理类模板,构造时是否加锁是可选的,在对象析构时如果持有锁会自动释放锁,所有权可以转移。对象生命期内允许手动加锁和释放锁。

scope_lock:严格基于作用域(scope-based)的锁管理类模板,构造时是否加锁是可选的(不加锁时假定当前线程已经获得锁的所有权),析构时自动释放锁,所有权不可转移,对象生存期内不允许手动加锁和释放锁。

share_lock:用于管理可转移和共享所有权的互斥对象。

  • scope_lock和lock_guard相比,可以简单的理解,在生命周期内,scope_lock不允许手动加锁和释放锁,而lock_guard可以。

二、条件变量

参见《【并发编程九】c++线程同步——条件变量(condition_variable)》(书写中。。。还未完成)

三、future

(书写中。。。还未完成)

四、信号量

(书写中。。。还未完成)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chrony服务器

Chrony是一个开源自由的网络时间协议 NTP 的客户端和服务器软软件。它能让计算机保持系统时钟与时钟服务器&#xff08;NTP&#xff09;同步&#xff0c;因此让你的计算机保持精确的时间&#xff0c;Chrony也可以作为服务端软件为其他计算机提供时间同步服务。 Chrony由两个程序…

【嵌入式Linux环境搭建-10】tftp服务安装、双网卡并行

10.tftp服务安装 板子有有线网卡&#xff0c;需有线连接到电脑 主机安装TFTP服务 $ sudo apt-get install xinetd tftpd tftp $ sudo vim /etc/xinetd.d/tftp /*添加下面内容, service tftp { protocol udp port 69 socket_type dgram wait …

aws lambda 理解RIC和RIE构建和测试容器镜像并通过cdk部署lambda函数

参考资料 AWS Lambda 的新功能 — 容器映像支持 快速构建基于 Lambda 容器镜像的 OCR 应用 利用 Lambda 容器镜像搭建分布式压测引擎 lambda容器镜像构建 可以将 Lambda 函数打包和部署最大 10 GB 的容器映像&#xff0c;轻松构建和部署依赖于大量依赖项的更大型工作负载 …

【Java核心技术】创建多线程

1、多线程的概述 什么是线程&#xff1f; 线程(thread)是一个程序内部的一条执行路径。 我们之前启动程序执行后&#xff0c;main方法的执行其实就是一条单独的执行路径。 public static void main(String[] args) {// 代码...for (int i 0; i < 10; i) {System.out.prin…

《柳叶刀》:约20%中国男性死亡可归因于吸烟

*仅供医学专业人士阅读参考 吸烟喝酒可谓众所周知的两大“健康刺客”。一首歌名为《给我一杯酒》中的歌词唱到“给我一杯酒&#xff0c;再给我一只烟&#xff0c;说走就走&#xff0c;我有的是时间”&#xff0c;传唱度极高&#xff0c;甚至还一度成为短视频平台的最热门的BGM之…

概论_第2章_重点内容__随机变量函数的概率分布

一 定义 概括地说&#xff1a; 随机变量Y是随机变量X的函数。 设g(x) 是一给定的连续函数&#xff0c; 称Yg(X) 为随机变量X的一个函数&#xff0c; Y也是一个随机变量。当X取值 时&#xff0c;Y取值 . ~~~~~~~~~~~~~~ 本文讨论连续型随机变量函数。 定理1: 设X为连续型…

Appium环境搭建及元素定位

01 Appium简介 Appium是一个开源测试自动化框架&#xff0c;可用于原生&#xff0c;混合和移动Web应用程序测试。它使用WebDriver 协议驱动iOS&#xff0c;Android和Windows应用程序。 01 环境搭建步骤 Appium环境安装&#xff1a; 第一步 安装 appium 桌面版客户端 Appium…

Linux挂载磁盘命令

需求&#xff1a; 只有一个硬盘&#xff0c;创建多个挂载点 参考&#xff1a;linux如何实现挂载—linux_是小明同学啊的博客-CSDN博客_linux挂载 1 查看当前挂载情况 执行 df -h&#xff0c; 查看当前 / 根目录下挂载的分区名&#xff0c;在下图中为倒数第三行&#xff0c; …

html练习

1. 2. 3. <h1>各科小常识</h1> <h3>语文</h3> <p> 三国演义是中国四大古典名著之一&#xff0c;元末明初小说家罗贯中所著。是中国第一部章回体历史演义的小说&#xff0c;描写了从东汉末年到西晋初年近100年的历史风云。</p> <hr&g…

Ubuntu20.04 LTS 安装 ros Noetic 树莓派4/PC

Ubuntu 20.04 LTS 安装树莓派系统. 主要参考了这两篇文章&#xff1a; https://blog.csdn.net/liangforcold/article/details/126385774 https://blog.csdn.net/yangcunbiao/article/details/123056806 https://blog.csdn.net/duping812/article/details/110391081 1、下载安…

【前端-NPM私服】内网使用verdaccio搭建私有npm服务器-docker搭建verdaccio流程

目录一、npm私服是什么1. 定义2. 为什么需要npm私服二、npm私服如何使用1. 链接到npm私服2. 注册私服账号3. 发布组件到私服4. 关联LDAP服务5. 提高下载速度三、私服搭建方案四、docker搭建Verdaccio流程1. 拉镜像2. 创建卷3. 启动容器4. 软链接卷到统一的目录5. 配置Verdaccio…

【node.js】npm包管理工具的使用

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;node.jsx中npm包管理工具的基础使用&#xff0c;包与依赖关系的介绍 下图为本文的核心 目…

rmq 主备自动切换模式

https://rocketmq.apache.org/zh/docs/deploymentOperations/16autoswitchdeploy/ https://github.com/apache/rocketmq/blob/develop/docs/cn/controller/design.md controller 端 leader选举 主备自动切换模式就是controller模式&#xff0c;controller可以嵌入name serve…

ccc-sklearn-12-线性回归(2)

一、非线性问题&#xff1a;多项式回归 主要探讨&#xff1a;通过线性回归解决非线性问题 数据的线性与非线性 通常情况下&#xff0c;分类问题中决策函数往往是一个分段函数&#xff0c;这个函数明显不满足可以用一条直线进行表示的属性&#xff0c;因此分类问题中特征与标签[…

若依整合第三方登录

0&#xff1a;以gitee为例&#xff0c;首先开通gitee第三方登录&#xff1a;&#xff08;在设置里面的第三方应用里面新建&#xff09; 0.1&#xff1a;后端引入JustAuth第三方登陆框架&#xff1a; <dependency><groupId>me.zhyd.oauth</groupId><artif…

H5UI库、加密技术和二维码

一、H5UI库 1. 使用方法&#xff1a; ​ &#xff08;1&#xff09;页面中引入css文件 ​ h5ui.css &#xff08;h5ui.min.css&#xff09; ​ &#xff08;2&#xff09;页面中引入js文件 ​ ​ jquery.min.js ​ ​ h5ui.min.js 2. 组件…

十五、Kubernetes中Pod生命周期详解、实例

1、概述 我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期&#xff0c;它主要包含下面的过程&#xff1a; pod创建过程 运行初始化容器&#xff08;init container&#xff09;过程 运行主容器&#xff08;main container&#xff09; 容器启动后钩子&#xff0…

对于负载均衡服务器一致性哈希算法一些简单的想法

文章目录一致性哈希负载均衡的介绍一致性哈希负载均衡的介绍 负载均衡这个概念可以抽象为&#xff1a;从n个候选服务器中选择一个进行通信。 负载均衡算法有&#xff1a;随机&#xff0c;轮询&#xff0c;最小连接数等。今天的“猪脚”是一致性哈希负载均衡算法&#xff1b; 一…

Java语法:枚举

1.枚举是什么&#xff1f; 枚举是Java中的一种特殊类型。 2.枚举的作用 是为了做信息的标志和分类。 3.枚举的语法 定义语法&#xff1a; 修饰符 enum 枚举名称 {第一行都是罗列枚举类实例的名称。 } /*** 枚举类*/ public enum Season {//枚举的第一行必须罗列枚举类的…

流量劫持的危害及应对方法

流量劫持总体来说属于中间人攻击的一种&#xff0c;本质上攻击者在通信两端之间对通信内容进行嗅探和篡改&#xff0c;以达到插入数据和获取关键信息的目的。目前互联网上发生的流量劫持基本是两种手段来实现的: 域名劫持&#xff1a;通过劫持掉域名的DNS解析结果&#xff0c;…