【C++ 并发与多线程】std::thread类-为共享数据加锁 2

2023-11-16

正交——消除无关事务之间的影响,力求高内聚低耦合。

 死锁的概念略去不说,死锁有可能发生在使用多个互斥量的场景下,也可能存在没有使用互斥量的场景:

  • 两个线程都在等待对方释放互斥量
  • 两个线程都调用了对方的join()函数

为了解决两个线程都在等待对方释放互斥量导致的死锁问题,C++11提供了若干机制:

  • std::lock()函数
  • std::unique_lock类

    锁住所有互斥量

    只要将互斥量作为参数传递给std::lock(),std::lock()就能够锁住多个互斥量。std::lock()并未指定解锁和上锁的顺序,其能够保证:

  • std::lock()执行成功时,所有互斥量都已经被上锁,并且没有死锁问题
  • std::lock()执行失败时,已被其上锁的互斥量都会被解锁
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock

class some_big_object
{
public:
	some_big_object(int a) :x(a) {}
	void Print() { std::cout << x << std::endl; }
private:
	int x;
};
class X
{
private:
	some_big_object& some_detail;
	std::mutex m;
public:
	X(some_big_object & sd) :some_detail(sd) {}
	friend void swap(X& lhs, X& rhs)
	{
		if (&lhs == &rhs)
			return;
		std::lock(lhs.m, rhs.m);
		std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
		std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
		std::swap(lhs.some_detail, rhs.some_detail);
	}
};

template<class T>
void swap(T& lhs, T& rhs);

template<>
void swap<some_big_object>(some_big_object &x, some_big_object &y)
{
	X a(x), b(y);
	swap(a, b);
}

int main()
{
	some_big_object a(1), b(2);
	a.Print(), b.Print();
	swap(a, b);
	a.Print(), b.Print();
	return 0;
}
------------------------------------------------------------
1
2
2
1
请按任意键继续. . .

上面一段代码使用了模板的偏特化特性,这里不需要深究,只需要知道swap(a, b)最终会调用X类的swap友元函数。在该友元函数中,std::lock()函数锁住两个互斥量,std::lock_guard负责unlock两个互斥量,如果不调用std::lock_guard(),需要手动unlock()。std::adopt_lock参数表示互斥量已经上锁,这里仅仅是不会重复上锁。下面两个例子起到相同作用。

// example 1
std::mutex mtx;
std::lock(mtx); // have to lock before the next sentence
std::lock_guard<std::mutex> guard(mtx, std::adopt_lock);

// example 2
std::mutex mtx;
std::lock(mtx);
mtx.unlock();

避免死锁的一点建议

C++并发编程中给出了几点避免死锁的进阶指导:

  • 1、避免嵌套锁
  • 2、避免在持有锁时调用用户提供的代码
  • 3、使用固定顺序获取锁
  • 4、使用锁的层次结构

前三个建议看字面意思就可以了,我们这里主要阐述锁的层次结构。层次锁需要遵守如下原则:

当代码试图对一个互斥量上锁,在该层锁已被低层持有时,上锁是不允许的。

hierarchical_mutex high_level_mutex(10000);
hierarchical_mutex low_level_mutex(7000); 
hierarchical_mutex low_level_mutex(5000); 

int do_low_level_stuff();
int low_level_func()
{
  std::lock_guard<hierarchical_mutex> lk(low_level_mutex); 
  return do_low_level_stuff();
}

void high_level_stuff(int some_param);
void high_level_func()
{
  std::lock_guard<hierarchical_mutex> lk(high_level_mutex); 
  high_level_stuff(low_level_func()); 
}

void middle_level_stuff(int some_param);
void middle_level_func()
{
  std::lock_guard<hierarchical_mutex> lk(middle_level_mutex); 
  middle_level_stuff(high_level_stuff()); 
}

int main()
{
    high_level_func();
    middle_level_func();
}

按照层次锁的原则,high_level_func()能够正确执行,而middle_level_func()不能正确执行:

  • high_level_func()先获取到高层级的锁,然后获取到低层级的锁,符合原则
  • middle_level_func()先获取低层级的锁,然后获取到高层级的锁,不符合原则
class hierarchical_mutex
{
  std::mutex internal_mutex;
  unsigned long const hierarchy_value;
  unsigned long previous_hierarchy_value;
  static thread_local unsigned long this_thread_hierarchy_value; 
  void check_for_hierarchy_violation()
  {
    if(this_thread_hierarchy_value <= hierarchy_value)
    {
      throw std::logic_error(“mutex hierarchy violated”);
    }
}
  void update_hierarchy_value()
  {
    previous_hierarchy_value=this_thread_hierarchy_value;
    this_thread_hierarchy_value=hierarchy_value;
  }
public:
  explicit hierarchical_mutex(unsigned long value):
      hierarchy_value(value),
      previous_hierarchy_value(0)
  {}
void lock() {
    check_for_hierarchy_violation();
    internal_mutex.lock();
    update_hierarchy_value();
    }
  void unlock()
  {
    this_thread_hierarchy_value=previous_hierarchy_value; 
    internal_mutex.unlock();
  }
  bool try_lock()
  {
    check_for_hierarchy_violation();
    if(!internal_mutex.try_lock())
      return false;
    update_hierarchy_value();
    return true;
} };
thread_local unsigned long hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX);

引用来源: C++并发编程2——为共享数据加锁(三)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++ 并发与多线程】std::thread类-为共享数据加锁 2 的相关文章

  • c++智能指针和普通指针之间相互转换

    include
  • 【C++】error LNK2019: 无法解析的外部符号

    转 C error LNK2019 无法解析的外部符号 错误解决方案 今天在实现类模板特例化的时候遇到一个问题 就是把类模板函数实现放到类的cpp文件中 然后在main函数中使用这个类的时候 就会出现无法解析的外部符号 函数名 xxxx 等
  • c++模板与泛型编程

    函数模板 template
  • c++类模板与继承

    继承中父子类和模板类的结合情况 1 父类是一般类 子类是模板 类 2 父类是模板类 子类是一般类 3 父类和子类都是模板类 1 include
  • C++之函数重载

    目录 1 函数重载 2 函数重载的概念 3 编译器的工作 4 判断函数重载的规则 1 函数的重复声明 2 参数表的比较过程与形参名无关 3 如果在两个函数的参数表中 4 相同的参数列表 5 形参是按值传递方式定义 6 参定义指针或引用 7
  • C++编程之构造函数

    学习到了C 的构造函数了 之前上的课比较简单加上自己也有一定的编程基础 所以一直都没觉得有什么压力 今天的课感觉有点难了 构造函数的作用 在对象被创建时 使用特定的值构造对象 将对象初始化为一个特定的初始状态 例如 希望在构造一个Clock
  • C++——内存分区

    内存分区模型 内存分区 四大分区 编译后运行前 程序运行后 栈区 堆区 1 new使用 2 释放空间 3 new 数组 内存分区 四大分区 代码区 二进制代码 操作系统管理 全局区 全局变量 静态变量 常量 栈区 编译器自动分配释放 函数的
  • STL源码:lis容器(Qt5.8版本)

    初次学习STL源码 很多语义尚且比较模糊 需待二次学习 源文件结构 主要的实现都在
  • C++之运算符重载

    目录 1 运算符重载 1 可实现的对象 编辑2 定义运算符重载函数的一般格式 3 运算符的重载实际 2 为什么要重载 3 前提 4 如何重载 5 默认 6 指针作为数据成员 7 字符串重载 8 友元重载 重载输出 lt lt 运算符 9 运
  • C++的一些概念 面向对象程序的基本特点

    本节主要介绍一些基本概念 有关于面向对象程序的基本特点 Markdown和扩展Markdown简洁的语法 抽象 对某一类对象的共同属性和行为进行概括 形成类 首先注意问题的本质和描述 其次是实现的过程或细节 数据抽象 描述某类对象的属性或状
  • 遍历Newtonsoft.Json.Linq.JObject

    JObject 遍历 引用命名空间 using Newtonsoft Json Linq JObject jObject JObject Parse ID 001 Mark Hello Word StringBuilder str new
  • 【C++笔记】数据结构栈、堆,内存占用中栈区、堆区的区别和理解

    在计算机领域 堆栈是一个不容忽视的概念 我们编写的C语言程序基本上都要用到 但对于很多的初学着来说 堆栈是一个很模糊的概念 堆栈 一种数据结构 一个在程序运行时用于存放的地方 这可能是很多初学者的认识 因为我曾经就是这么想的和汇编语言中的堆
  • C++指针的注意事项

    1 定义指针时 切勿忘记初始化 2 当指针作为函数参数时 函数体内记得判空 3 动态申请内存时 不要忘记判断内存申请是否成功 4 勿忘释放内存 并且只释放堆内存 即动态申请的内存 5 内存释放后 记得将指针置空 6 函数返回值一定不要是局部
  • Unable to cast COM object of type 'System.__ComObject' to interface type 'Microsoft.Office.Interop.E

    前段时间做了个将Txt中数据导出到Excel中的C 小应用程序 一直都运行很好的 今天突然有同事安装时 报如下错 Exception Text System InvalidCastException Unable to cast COM o
  • C语言--八大排序之直接插入排序算法

    排序 把无序的数据变得有序 默认升序 笔试面试排名第一的内容 1 直接 简单 插入排序 例如 扑克牌发牌时 每发一张 将牌有序插入 从当前位置开始 从后往前找比当前数字小的 找到后插入到这个小的数字后面 在找的过程中 如果发现一个比当前数字
  • C# new与malloc

    目录 C new与malloc C new与malloc的区别 C new关键字底层做的操作 C new与malloc new关键字 new关键字在C 中用于实例化对象 并为其分配内存 它是面向对象编程的基本操作之一 使用new关键字可以在
  • C语言用牛顿迭代法和二分法递归求解三元一次方程

    求解方程 2x 3 4x 2 3x 6 0 牛顿迭代法 牛顿迭代法公式 以下图片均来源于百度 牛顿迭代法用递归实现解三元一次方程 include
  • 函数重载 隐藏 重写 覆盖

    重载 Overload 重载是比较容易弄明白的 定义 重载是指不同的函数使用相同的函数名 但是函数的参数个数或类型不同 参数列表不同 调用的时候根据函数的参数来区别不同的函数 函数重载跟返回值无关 规则 函数名相同 必须具有不同的参数列表
  • 【C++笔记】NULL、0、nullptr区别分析

    一 C的NULL 在C语言中 我们一般使NULL表示空指针 即 int i NULL foo t f NULL 但是 实际上在C语言中 NULL通常被定义为 define NULL void 0 也就是说NULL实际上是一个void 的指针
  • 【C语言】进制输出加上前缀

    对于八进制数字 它没法和十进制 十六进制区分 因为八进制 十进制和十六进制都包含 0 7 这几个数字 对于十进制数字 它没法和十六进制区分 因为十六进制也包含 0 9 这几个数字 如果十进制数字中还不包含 8 和 9 那么也不能和八进制区分

随机推荐

  • 2013年9月24日星期二(demo5_1参数化2D直线)

    现在很想封装下3D的 将所谓的难度踏在脚下 好 现在准备了 代码到哪里 分析到哪里 首先 包含各种头文件 include common h include DDraw Interface h using namespace std HWND
  • iMazing传输 iPhone 备忘录和通话记录功能

    对于经常需要进行客户联系的业务员来说 通过整理通话记录 能够统计到拜访客户的次数 效果等数据 如果是通过手动统计的方式 将耗费大量的时间与精力 iMazing为苹果设备用户提供了功能齐全的通话管理功能 用户可以通过使用该功能 查询相关的通话
  • 使用Python搭建代理服务器- 爬虫代理服务器详细指南

    搭建一个Python爬虫代理服务器可以让你更方便地管理和使用代理IP 下面是一个详细的教程来帮助你搭建一个简单的Python爬虫代理服务器 1 首先 确保你已经安装了Python 你可以在官方网站 https www python org
  • 打开c语言生成exe文件,出现闪退的解决方法

    额 在给大一学弟上第一节实验课的时候 经常有学弟问我 为什么打开c语言生成的exe文件 立马闪退 起初个别问的时候 我只是简单的说明程序运行完了 就自动关了 现在先不用涉及这个 以后自然懂了 但是后来问的人多了 我就觉得有必要先给他们提一下
  • Entity Framework Core系列教程-24-使用存储过程

    在Entity Framework Core中使用存储过程 在这里 您将学习如何在Entity Framework Core中执行数据库存储过程 EF Core提供了以下方法来执行存储过程 DbSet
  • 【总结】C++各种进制转换函数汇总

    文章目录 前言 一 指定格式转换输出 二 任意2 36进制数转化为10进制数 三 10进制数转换为任意的n进制数 四 使用字符串流string stream进制转换 前言 进制之间的转换有两种方法 自定义进制转换函数 将一个n进制的数转换为
  • FFmpeg中AVDictionary介绍

    FFmpeg中的AVDictionary是一个结构体 简单的key value存储 经常使用AVDictionary设置或读取内部参数 声明如下 具体实现在libavutil模块中的dict c h 提供此结构体是为了与libav兼容 但它
  • gcc命令生成静态库和动态库

    一 基本概念1 1什么是库在windows 平台和linux 平台下都大量存在着库 本质上来说库是一种可执行代码的二进制形式 可以被操作系统载入内存执行 由于windows 和linux 的平台不同 主要是编译器 汇编器和连接器的不同 因此
  • pandas 数据结构

    Series常用操作 知道 Series创建 传入一个Python列表 如果传入的数据类型是统一的数字 那么最终的dtype类型是int64 如果传入的数据类型是统一的字符串 那么最终的dtype类型是object 如果传入的数据类型是多种
  • TensorFlow搭建CNN-LSTM混合模型实现多变量多步长时间序列预测(负荷预测)

    目录 I 前言 II CNN LSTM III 代码实现 3 1 数据处理 3 2 模型训练 测试 3 3 实验结果 IV 源码及数据 I 前言 前面已经写了很多关于时间序列预测的文章 深入理解PyTorch中LSTM的输入和输出 从inp
  • input标签限制只能输入数字

  • 操作系统实验:银行家算法(C语言)

    实验内容 某系统中进程P1 P2 P3 Pn 同时请求资源R1 R2 R3 Rn 已知t0时刻资源分配情况 参考下表 1 编写程序 分析当前系统状态是否安全 若系统安全 请输出安全序列 2 在系统安全的情况下 若有进程提出资源请求 如t1时
  • 人脸识别之caffe-face

    该论文为2016年的一篇ECCV ADiscriminative Feature Learning Approach for Deep Face Recognition 还是深圳先进院乔宇老师组的一篇 不管是思想还是效果都非常的棒 论文思想
  • Perl调用shell命令方法小结(system/反引号/exec)

    system 反引号 exec 为避免shell命令的特殊符号采用先变量定义的方法 system perl也可以用system调用shell的命令 它和awk的system一样 返回值也是它调用的命令的退出状态 root AX3sp2 ca
  • 单片机控制继电器实验

    单片机控制继电器实验 在各种自动控制设备中 都存在一个低压的自动控制电路与高压电气电路的互相连接问题 一方面要使低压的电子电路的控制信号能够控制高压电气电路的执行元件 如电动机 电磁铁 电灯等 另一方面又要为电子线路的电气电路提供良好的电隔
  • 如何安装以及用相关插件配置OBSIDIAN?

    之前看人推荐logseq就尝试了一下 吐槽跨设备同步难搞 然后被人种草了Obsidian 这几天使用下来感觉确实比logseq好用多了 第三方插件同步虽然有点冲突但大体上还好 现在考虑怎么去合理规划tag以及wiznote笔记的迁移 虽说感
  • 刷脸支付机会是留给敢迈出第一步的人

    从支付宝推出余额宝 花呗等功能大幅培养用户理财习惯 到微信支付通过微信红包打通朋友圈 微信支付和支付宝的擂台之战从未平息 面对10亿交易笔数到20亿的跨越 较量已经从扫码支付延伸至了各式各样的甚至难啃的场景 比如停车场无感支付 线下的刷脸支
  • Docker笔记(精简版)

    文章目录 初始Docker 学习背景 Docker解决依赖兼容问题 Docker解决操作系统环境差异 Docker架构 镜像和容器 DockerHub Docker架构 安装Docker 卸载 可选 安装Docker 启动docker 配置
  • VS静态编译C/C++解决程序丢失 VCRUNTIME140.dll

    VS静态编译C C 解决程序丢失 VCRUNTIME140 dll1VS静态编译C C 解决程序丢失 VCRUNTIME140 dll2VS静态编译C C 解决程序丢失 VCRUNTIME140 dll3VS静态编译C C 解决程序丢失 V
  • 【C++ 并发与多线程】std::thread类-为共享数据加锁 2

    正交 消除无关事务之间的影响 力求高内聚低耦合 死锁的概念略去不说 死锁有可能发生在使用多个互斥量的场景下 也可能存在没有使用互斥量的场景 两个线程都在等待对方释放互斥量 两个线程都调用了对方的join 函数 为了解决两个线程都在等待对方释