高阶开发基础——快速入门C++并发编程2

目录

常见的几个经典错误——1: 关于内存访问的几个经典的错误

解决多线程流对单一数据的写问题

std::mutex


常见的几个经典错误——1: 关于内存访问的几个经典的错误

为了深入的理解一些潜在的错误,笔者设计了一个这样的样例,各位请看:

#include <chrono>
#include <thread>
​
​
std::thread th;
void inc_ref(int &ref) {std::this_thread::sleep_for(std::chrono::seconds(1));ref++;
}
​
void worker() {int value = 1;th = std::thread(inc_ref, std::ref(value));
}
​
int main()
{worker();th.join();
}

你发现问题了嘛?

答案是,worker是一个同步函数,我们的value的作用域隶属于worker函数的范围内,现在,worker一旦将th线程派发出去,里面的工作函数的引用ref就会非法。现在,我们对一个非法的变量自增,自然就会爆错

➜  make
[ 50%] Building CXX object CMakeFiles/demo.dir/main.cpp.o
[100%] Linking CXX executable demo
[100%] Built target demo
➜  ./demo 
Segmentation fault (core dumped)

扩展:

  1. 关于C++的时间库,C++的时间库抽象了几个经典的必要的时间操作,因此,使用这个库来表达我们的时间非常的方便。

  • std::chrono::duration: 表示时间间隔,例如 5 秒、10 毫秒等。

  • std::chrono::time_point: 表示一个时间点,通常是相对于某个纪元(如 1970 年 1 月 1 日)的时间。

  • std::chrono::system_clock: 系统范围的实时时钟,表示当前的日历时间。

  • std::chrono::steady_clock: 单调时钟,适用于测量时间间隔,不会受到系统时间调整的影响。

  • std::chrono::high_resolution_clock: 高精度时钟,通常是 steady_clocksystem_clock 的别名。

#include <iostream>
#include <chrono>
​
int main() {// 定义一个 5 秒的时间间隔std::chrono::seconds duration(5);
​// 获取当前时间点auto start = std::chrono::steady_clock::now();
​// 模拟一些操作std::this_thread::sleep_for(duration);
​// 获取结束时间点auto end = std::chrono::steady_clock::now();
​// 计算时间间隔auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
​std::cout << "Elapsed time: " << elapsed.count() << " ms" << std::endl;
​return 0;
}
  1. std::this_thread::sleep_for

std::this_thread::sleep_for 是 C++11 引入的函数,用于使当前线程休眠指定的时间。它接受一个 std::chrono::duration 类型的参数,表示休眠的时间长度。这个this_thread表达的是当前的运行线程的线程句柄,也就是说,调用这个函数表达了运行这个函数的当前线程实体休眠若干秒

#include <iostream>
#include <thread>
#include <chrono>
​
int main() {std::cout << "Sleeping for 2 seconds..." << std::endl;
​// 使当前线程休眠 2 秒std::this_thread::sleep_for(std::chrono::seconds(2));
​std::cout << "Awake!" << std::endl;
​return 0;
}

笔者在这里故意延迟,是为了让worker有时间清理他自己的函数栈,让程序必然报错。但是在项目中,很有可能线程在worker清理完之前就执行完成了,这会导致给项目埋雷,引入非常大的不确定性。

这样的例子还有对堆上的内存,这同样管用:

#include <chrono>
#include <thread>
#include <print>
​
std::thread th;
void inc_ref(int* ref) {std::this_thread::sleep_for(std::chrono::seconds(1));(*ref)++;std::print("value of the ref points to is {}\n", *ref);
}
​
void worker() {int* value = new int;*value = 1;th = std::thread(inc_ref, value);delete value; // free immediately
}
​
int main()
{worker();th.join();
}

程序一样会崩溃,这里,我们没有协调内存块的声明周期。一个很好的办法就是使用智能指针,关于智能指针,笔者再最早期写过两篇博客介绍:

C++智能指针1

C++智能指针2

也就是保持变量的引用到最后一刻,不用就释放!

解决多线程流对单一数据的写问题

可变性!并发编程中一个最大的议题就是讨论对贡献数据的写同步性问题。现在,我们来看一个例子

static int shared_value;
void worker_no_promise() {for (int i = 0; i < 1000000; i++) {shared_value++;}
}
​
int main() {auto thread1 = std::thread(worker_no_promise);auto thread2 = std::thread(worker_no_promise);thread1.join();thread2.join();
​std::print("value of shared is: {}\n", shared_value);
}

你猜到问题了嘛?

➜  make && ./demo
[ 50%] Building CXX object CMakeFiles/demo.dir/main.cpp.o
[100%] Linking CXX executable demo
[100%] Built target demo
value of shared is: 1173798

奇怪?为什么不是200000呢?答案是非原子操作!这是因为一个简单的自增需要——将数据从内存中加载,操作,放回内存中——遵循这三个基本的步骤。所以,可能我们的两个线程从内存中加载了同样的数,自增放回去了也就会是相同的数,两个线程实际上只加了一次。甚至,可能没有增加!(这个你可以自己画图求解,笔者不在这里花费时间了)

如何处理,我们下面引出的是锁这个概念。

std::mutex

mutex就是锁的意思。它的意思很明白——那就是对共享的数据部分进行“上锁”,当一个线程持有了这个锁后,其他的线程想要请求这个锁就必须门外竖着——阻塞等待。

static int shared_value;
std::mutex mtx;
void worker() {for (int i = 0; i < 1000000; i++) {mtx.lock();shared_value++;mtx.unlock();}
}
​
int main() {auto thread1 = std::thread(worker);auto thread2 = std::thread(worker);thread1.join();thread2.join();
​std::print("value of shared is: {}\n", shared_value);
}

现在我们的结果相当好!

➜  make && ./demo
[ 50%] Building CXX object CMakeFiles/demo.dir/main.cpp.o
[100%] Linking CXX executable demo
[100%] Built target demo
value of shared is: 2000000

事实上,只要不超过INT64_MAX的值,相加的结果都应该是正确的!

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

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

相关文章

【leetcode100】路径总和Ⅲ

1、题目描述 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节点…

解锁数据结构密码:层次树与自引用树的设计艺术与API实践

1. 引言&#xff1a;为什么选择层次树和自引用树&#xff1f; 数据结构是编程中的基石之一&#xff0c;尤其是在处理复杂关系和层次化数据时&#xff0c;树形结构常常是最佳选择。层次树&#xff08;Hierarchical Tree&#xff09;和自引用树&#xff08;Self-referencing Tree…

python-leetcode-二叉树的层序遍历

102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right from coll…

c++可变参数详解

目录 引言 库的基本功能 va_start 宏: va_arg 宏 va_end 宏 va_copy 宏 使用 处理可变参数代码 C11可变参数模板 基本概念 sizeof... 运算符 包扩展 引言 在C编程中&#xff0c;处理不确定数量的参数是一个常见的需求。为了支持这种需求&#xff0c;C标准库提供了 &…

w191教师工作量管理系统的设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

Vuex状态管理

1、Vuex 是什么&#xff1f; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 简单理解 Vuex可以帮我们管理全局的属性&#xff0c;并且是是响应式的&…

DBASE DBF数据库文件解析

基于Java实现DBase DBF文件的解析和显示 JDK19编译运行&#xff0c;实现了数据库字段和数据解析显示。 首先解析数据库文件头代码 byte bytes[] Files.readAllBytes(Paths.get(file));BinaryBufferArray bis new BinaryBufferArray(bytes);DBF dbf new DBF();dbf.VersionN…

亚博microros小车-原生ubuntu支持系列:20 ROS Robot APP建图

依赖工程 新建工程laserscan_to_point_publisher src/laserscan_to_point_publisher/laserscan_to_point_publisher/目录下新建文件laserscan_to_point_publish.py #!/usr/bin/env python3import rclpy from rclpy.node import Node from geometry_msgs.msg import PoseStam…

冷启动+强化学习:DeepSeek-R1 的原理详解——无需监督数据的推理能力进化之路

本文基于 DeepSeek 官方论文进行分析,论文地址为:https://github.com/deepseek-ai/DeepSeek-R1/blob/main/DeepSeek_R1.pdf 有不足之处欢迎评论区交流 原文翻译 在阅读和理解一篇复杂的技术论文时,逐字翻译是一个重要的步骤。它不仅能帮助我们准确把握作者的原意,还能为后续…

优选算法的灵动之章:双指针专题(一)

个人主页&#xff1a;手握风云 专栏&#xff1a;算法 一、双指针算法思想 双指针算法主要用于处理数组、链表等线性数据结构中的问题。它通过设置两个指针&#xff0c;在数据结构上进行遍历和操作&#xff0c;从而实现高效解决问题。 二、算法题精讲 2.1. 查找总价格为目标值…

数据结构之栈和队列(超详解)

文章目录 概念与结构栈队列 代码实现栈栈是否为空&#xff0c;取栈顶数据、栈的有效个数 队列入队列出队列队列判空&#xff0c;取队头、队尾数据&#xff0c;队列的有效个数 算法题解有效的括号用队列实现栈用栈实现队列复用 设计循环队列数组结构实现循环队列构造、销毁循环队…

解析 Oracle 中的 ALL_SYNONYMS 和 ALL_VIEWS 视图:查找同义词与视图的基础操作

目录 前言1. ALL_SYNONYMS 视图2. ALL_VIEWS 视图3. 扩展 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 1. ALL_SYNONYMS 视图 在 Oracle 数据库中&#xff0c;同义词&#xff08;Synonym&#xff09;是对数…

DeepSeek-R1 本地部署教程(超简版)

文章目录 一、DeepSeek相关网站二、DeepSeek-R1硬件要求三、本地部署DeepSeek-R11. 安装Ollama1.1 Windows1.2 Linux1.3 macOS 2. 下载和运行DeepSeek模型3. 列出本地已下载的模型 四、Ollama命令大全五、常见问题解决附&#xff1a;DeepSeek模型资源 一、DeepSeek相关网站 官…

【C语言入门】解锁核心关键字的终极奥秘与实战应用(二)

目录 一、sizeof 1.1. 作用 2.2. 代码示例 二、const 2.1. 作用 2.2. 代码示例 三、signed 和 unsigned 3.1. 作用 3.2. 代码示例 四、struct、union、enum 4.1. struct&#xff08;结构体&#xff09; 4.1.1. 作用 4.1.2. 代码示例 4.2. union&#xff08;联合…

如何确认Linux嵌入式系统的触摸屏对应的是哪个设备文件?如何查看系统中所有的输入设备?输入设备的设备文件有什么特点?

Linux嵌入式系统的输入设备的设备文件有什么特点&#xff1f; 在 Linux 中&#xff0c;所有的输入设备&#xff08;如键盘、鼠标、触摸屏等&#xff09;都会被内核识别为 输入事件设备&#xff0c;并在 /dev/input/ 目录下创建相应的 设备文件&#xff0c;通常是&#xff1a; …

ESP32-c3实现获取土壤湿度(ADC模拟量)

1硬件实物图 2引脚定义 3使用说明 4实例代码 // 定义土壤湿度传感器连接的模拟输入引脚 const int soilMoisturePin 2; // 假设连接到GPIO2void setup() {// 初始化串口通信Serial.begin(115200); }void loop() {// 读取土壤湿度传感器的模拟值int sensorValue analogRead…

Hive:窗口函数(1)

窗口函数 窗口函数OVER()用于定义一个窗口&#xff0c;该窗口指定了函数应用的数据范围 对窗口数据进行分区 partition by 必须和over () 一起使用, distribute by经常和sort by 一起使用,可以不和over() 一起使用.DISTRIBUTE BY决定了数据如何分布到不同的Reducer上&#xf…

【react-redux】react-redux中的 useDispatch和useSelector的使用与原理解析

一、useSelector 首先&#xff0c;useSelector的作用是获取redux store中的数据。 下面就是源码&#xff0c;感觉它的定义就是首先是createSelectorHook这个方法先获得到redux的上下文对象。 然后从上下文对象中获取store数据。然后从store中得到选择的数据。 2、useDispatc…

java异常处理——try catch finally

单个异常处理 1.当try里的代码发生了catch里指定类型的异常之后&#xff0c;才会执行catch里的代码&#xff0c;程序正常执行到结尾 2.如果try里的代码发生了非catch指定类型的异常&#xff0c;则会强制停止程序&#xff0c;报错 3.finally修饰的代码一定会执行&#xff0c;除…

传输层协议 UDP 与 TCP

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; 前置复盘&#x1f98b; 传输层&#x1f98b; 再谈端口号&#x1f98b; 端口号范围划分&#x1f98b; 认识知名端口号 (Well-Know Port Number) 二&#xf…