高阶开发基础——快速入门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的值,相加的结果都应该是正确的!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【工欲善其事】利用 DeepSeek 实现复杂 Git 操作:从原项目剥离出子版本树并同步到新的代码库中

文章目录 利用 DeepSeek 实现复杂 Git 操作1 背景介绍2 需求描述3 思路分析4 实现过程4.1 第一次需求确认4.2 第二次需求确认4.3 第三次需求确认4.4 V3 模型&#xff1a;中间结果的处理4.5 方案验证&#xff0c;首战告捷 5 总结复盘 利用 DeepSeek 实现复杂 Git 操作 1 背景介绍…

CommonJS

CommonJS 是由 JavaScript 社区于 2oo9 年提出的包含模块、文件、IO、控制台在内的一系列标准。Node.js 的实现中采用了 CommonJS 标准的一部分&#xff0c;并在其基础上进行了一些调整。我们所说的 CommonJS 模块和 Node.js 中的实现并不完全一样&#xff0c;现在一般谈到 Com…

AP单类平均准确率

P_true N_true P_pred TP Fp N_pred FN TNP NTP&#xff08;真正样本&#xff0c;与真实框IoU大于阈值的框&#xff09; FP&#xff08;假正样本&#xff0c;与真实框IoU小于阈值的框&#xff09; TN&#xff08;真负样本&#xff0c;背景&#xff09;…

MQTT实战之在vue和java中使用

在VUE中使用MQTT 1、创建vue项目&#xff08;使用node版本为20.12.0&#xff09; >>npm create vitelatest Need to install the following packages: create-vite6.1.1 Ok to proceed? (y) y √ Project name: ... mqtt-vue √ Select a framework: Vue √ Select a v…

oracle: 表分区>>范围分区,列表分区,散列分区/哈希分区,间隔分区,参考分区,组合分区,子分区/复合分区/组合分区

分区表 是将一个逻辑上的大表按照特定的规则划分为多个物理上的子表&#xff0c;这些子表称为分区。 分区可以基于不同的维度&#xff0c;如时间、数值范围、字符串值等&#xff0c;将数据分散存储在不同的分区 中&#xff0c;以提高数据管理的效率和查询性能&#xff0c;同时…

数据分析系列--⑥RapidMiner构建决策树(泰坦尼克号案例含数据)

一、资源下载 二、数据处理 1.导入数据 2.数据预处理 三、构建模型 1.构建决策树 2.划分训练集和测试集 3.应用模型 4.结果分析 一、资源下载 点击下载数据集 二、数据处理 1.导入数据 2.数据预处理 三、构建模型 1.构建决策树 虽然决策树已经构建,但对于大多数初学者或…

Unity游戏(Assault空对地打击)开发(4) 碰撞体和刚体的添加

前言 飞机和世界的大小关系不太对&#xff0c;我稍微缩小了一下飞机。 详细步骤 选中所有地形对象&#xff0c;如果没有圈起的部分&#xff0c;点击Add Component搜索添加。 接着选中Player对象&#xff0c;添加这两个组件&#xff0c;最好&#xff08;仅对于本项目开发&#x…

【Windows7和Windows10下从零搭建Qt+Leaflet开发环境】

Windows7和Windows10下从零搭建QtLeaflet开发环境 本文开始编写于2025年1月27日星期一&#xff08;农历&#xff1a;腊月二十八&#xff0c;苦逼的人&#xff0c;过年了还在忙工作&#xff09;。 第一章 概述 整个开发环境搭建需要的资源&#xff1a; 操作系统 Windows7_x6…

【游戏设计原理】97 - 空间感知

一、游戏空间的类型 将游戏设计中的空间设计单独提取出来&#xff0c;可以根据其结构、功能和玩家的交互方式划分为以下几种主要类型。这些类型可以单独存在&#xff0c;也可以组合使用&#xff0c;以创造更加复杂和有趣的游戏体验。 1. 线性空间 定义&#xff1a;空间设计是…

基于云计算、大数据与YOLO设计的火灾/火焰目标检测

摘要&#xff1a;本研究针对火灾早期预警检测需求&#xff0c;采用在Kaggle平台获取数据、采用云计算部署的方式&#xff0c;以YOLO11构建模型&#xff0c;使用云计算服务器训练模型。经训练&#xff0c;box loss从约3.5降至1.0&#xff0c;cls loss从约4.0降至1.0&#xff0c;…

【大数据技术】教程03:本机PyCharm远程连接虚拟机Python

本机PyCharm远程连接虚拟机Python 注意:本文需要使用PyCharm专业版。 pycharm-professional-2024.1.4VMware Workstation Pro 16CentOS-Stream-10-latest-x86_64-dvd1.iso写在前面 本文主要介绍如何使用本地PyCharm远程连接虚拟机,运行Python脚本,提高编程效率。 注意: …

bypass hcaptcha、hcaptcha逆向

可以过steam&#xff0c;已支持并发&#xff0c;欢迎询问&#xff01; 有事危&#xff0c;ProfessorLuoMing

Mac本地部署DeekSeek-R1下载太慢怎么办?

Ubuntu 24 本地安装DeekSeek-R1 在命令行先安装ollama curl -fsSL https://ollama.com/install.sh | sh 下载太慢&#xff0c;使用讯雷&#xff0c;mac版下载链接 https://ollama.com/download/Ollama-darwin.zip 进入网站 deepseek-r1:8b&#xff0c;看内存大小4G就8B模型 …

VSCode设置内容字体大小

1、打开VSCode软件&#xff0c;点击左下角的“图标”&#xff0c;选择“Setting”。 在命令面板中的Font Size处选择适合自己的字体大小。 2、对比Font Size值为14与20下的字体大小。

嵌入式学习---蜂鸣器篇

1. 蜂鸣器分类 蜂鸣器是一种电子发声器件&#xff0c;采用直流电压供电&#xff0c;能够发出声音。广泛应用于计算机、打印机、报警器、电子玩具等电子产品中作为发声部件。一般仅从外形不易分辨蜂鸣器的种类。但是有些蜂鸣器使用广泛&#xff0c;见得多了就很容易分辨。例如常…

解析PHP文件路径相关常量

PHP文件路径相关常量包括以下几个常量&#xff1a; __FILE__&#xff1a;表示当前文件的绝对路径&#xff0c;包括文件名。 __DIR__&#xff1a;表示当前文件所在的目录的绝对路径&#xff0c;不包括文件名。 dirname(__FILE__)&#xff1a;等同于__DIR__&#xff0c;表示当前…

C++底层学习预备:模板初阶

文章目录 1.编程范式2.函数模板2.1 函数模板概念2.2 函数模板原理2.3 函数模板实例化2.3.1 隐式实例化2.3.2 显式实例化 2.4 模板参数的匹配原则 3.类模板希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 进入STL库学习之前我们要先了解有关模板的…

【腾讯前端面试】纯css画图形

之前参加腾讯面试&#xff0c;第一轮是笔试&#xff0c;面试官发的试卷里有一题手写css画一个扇形、一个平行四边形……笔试时间还是比较充裕的&#xff0c;但是我对这题完全没有思路&#x1f62d;于是就空着了&#xff0c;最后也没过。 今天偶然翻到廖雪峰大佬的博客里提到了关…

智慧园区综合管理系统如何实现多个维度的高效管理与安全风险控制

内容概要 在当前快速发展的城市环境中&#xff0c;智慧园区综合管理系统正在成为各类园区管理的重要工具&#xff0c;无论是工业园、产业园、物流园&#xff0c;还是写字楼与公寓&#xff0c;都在积极寻求如何提升管理效率和保障安全。通过快鲸智慧园区管理系统&#xff0c;用…

自制虚拟机(C/C++)(三、做成标准GUI Windows软件,扩展指令集,直接支持img软盘)

开源地址:VMwork 要使终端不弹出&#xff0c; #pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") 还要实现jmp near 0x01类似的 本次的main.cpp #include <graphics.h> #include <conio.h> #include <windows.h> #includ…