C++并发:线程管控
1 线程基本管控
每个C++程序都含有至少一个线程,即运行main()的线程,它由C++运行时系统启动。随后程序可以发起更多线程,它们以别的函数作为入口。这些新线程连同起始线程并发运行。当main()返回时,程序就会退出;同样,当入口函数返回时,对应的线程随之终结。如果借std::thread对象管控线程,即可选择等他结束。
1.1 发起线程
线程通过构造std::thread对象而启动,该对象指明线程要运行的任务。
void do_some_work();
std::thread myThread(do_some_work);
任何可调用类型都适用于std::thread。所以,作为代替,可以设计一个带有函数调用操作符的类(应当是下面的operator)
class background_task
{
public:void operator() () const{do_something();do_something_else();}
};background_task f;
std::thread my_thread(f);
f被复制到属于新线程的存储空间中,在那里被调用,由新线程执行。
1.1.1 与函数声明进行区分
如果传入std::thread的是临时变量,不是具名变量,那么调用构造函数的语法有可能与函数声明相同。这种情况,编译器会将其解释成函数声明。
声明为函数:函数名为my_thread,只接收一个参数,返回std::thread对象
std::thread my_thread(background_task());可以通过多用一对圆括号或使用新式的统一初始化语法
std::thread my_thread((background_task()));
std::thread my_thread{background_task()};还可以使用lambda表达式
std::thread my_thread([]{do_something();do_something_else();
});
1.2 汇合与分离
在启动线程后,需要明确是要等待他结束(也就是汇合)还是任由他独立运行(也就是分离)。如果等到std::thread销毁的时候还没有决定好,那么std::thread的析构函数将调用std::terminate()终止整个程序。
如果选择了分离,且分离时新线程还未运行结束,那将继续运行,甚至在std::thread对象销毁很久之后依然运行,它只有最终从线程函数返回时才会结束运行。
假设程序不等待线程结束,那么在线程运行结束前,我们要保证它所访问的外部数据始终正确,有效。由于使用多线程,所以我们可能会经常面临对象生存期的问题。比如下面的案例:
struct func
{int& i;func(int& i_):i(i_){}void operator() (){for (unsigned j=0; j<1000000; ++j) {do_something(i); 隐患:可能访问悬空引用}}
};void oops()
{int some_local_state=0;func my_func(some_local_state);std::thread my_thread(my_func);my_thread.detach(); 不等待新线程结束新线程可能仍运行,而主线程的函数却已经结束
}
| 主线程 | 新线程 |
| 构造my_func对象,引用局部变量some_local_state | |
| 通过my_thread对象启动新线程 | |
| 新线程启动 | |
| 调用func::operator() | |
| 分离新线程my_thread | 运行func::operator(); 调用do_something()函数, 进而引用局部变量some_local_state |
| 销毁局部变量some_local_state | 仍在运行 |
| 退出oops() | 继续运行func::operator(); 调用do_something()函数, 进而引用some_local_state, 导致未定义行为 |
因此:以下做法不可取:意图在函数中创建线程,并让线程访问函数的局部变量。除非线程肯定会在该函数退出前结束。或者是汇合新线程,此举可以保证在主线程的函数退出前,新线程执行完毕。
1.2.1 join—等待线程完成
若需等待线程完成,那么可以在与之关联的std::thread实例上,通过调用成员函数join()实现。对于上面的代码,就是把detach换成join。就能够保证在oops退出前,新线程结束。
对于一个线程,join仅能被调用一次,被调用后线程不再可汇合,成员函数joinable将返回false。
要注意,如果线程启动后有异常抛出,而join尚未执行,该join调用会被略过。
使用thread_guard保证在抛出异常时,退出路径的先后顺序与不抛出异常时一致。
也就是在析构函数中调用join
class thread_guard {std::thread& t;
public:explicit thread_guard(std::thread& t_) : t(t_){}~thread_guard() {if (t.joinable()) {t.join();}}thread_guard(thread_guard const&)=delete;thread_guard& operator=(thread_guard const&)=delete;
};struct func {int& i;explicit func(int& i_) : i(i_) {};void operator() () {for (unsigned j = 0; j < 1000000; ++j) {do_somthing();}}
};void f() {int some_local_state=0;func my_func(some_local_state);std::thread t(my_func);thread_guard g(t);do_something_in_current_thread();
}
1.2.2 detach—在后台运行线程
会令线程在后台运行,因此与之无法直接通信。其归属权和控制权都交给了C++运行时库,由此保证,一旦线程退出,与之关联的资源都会被正确回收。
只有在joinable返回true时,才能调用detach。
2 向线程函数传递参数
直接向std::thread的构造函数增添更多参数即可。需要注意的是,线程具有内部存储空间,参数会按照默认方式先复制到该处,新创建的执行线程才能直接访问它们。然后,这些副本被当成临时变量,以右值的形式传给新线程上的函数或可调用对象。即便函数相关参数按设想应该是引用,上述过程依然会发生。
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");void f(int i, std::string const& s);
void oops(int some_param)
{char buffer[1024];sprintf(buffer, "%i", some_param);std::thread t(f, 3, buffer);// std::thread t(f, 3, std::string(buffer));t.detach();
}
但是上述例子将字符串的引用复制到了thread的存储空间,当调用thread的外层函数销毁时,buffer将不存在,无法访问这个引用。可以使用注释里的方法,先转换成std::string对象(buffer相当于一个指针)
void update_data_for_widget(widget_id w, widget_data& data);
void oops_again(widget_id w)
{widget_data data;std::thread t(update_data_for_widget, w, data);display_status();t.join();process_widget_data(data);
}
根据update_data_for_widget函数的声明,第二个参数会以引用的方式传入update_data_for_widget,但是std::thread的构造函数并不知情,会直接复制提供的值。随后线程库内部会把参数副本当成move-only(只移型别),以右值的形式传递。最终,update_data_for_widget会收到右值,因为update_data_for_widget预期接受非const引用,我们不能向他传递右值。
解决方法是,按照如下方式改写(std::ref)
std::thread t(update_data_for_widget, w, std::ref(data));
这样就保证了传入update_data_for_widget函数的不是变量data的临时副本,而是指向变量data的引用,因此能够编译成功。
2.2 调用对象的方法
若要调用一个对象对应的方法,则需要传递方法地址和对象地址,第三个参数作为该方法的第一个入参。
class X {
public:void do_lengthy_work();};
X my_x;
std::thread t(&X::do_lengthy_work, &my_x);
上述代码调用对象my_x的do_lengthy_work方法。
2.3 只能移动的方式传递参数
3 移交线程归属权
相关文章:
C++并发:线程管控
1 线程基本管控 每个C程序都含有至少一个线程,即运行main()的线程,它由C运行时系统启动。随后程序可以发起更多线程,它们以别的函数作为入口。这些新线程连同起始线程并发运行。当main()返回时,程序就会退出;同样&…...
C++ 设计模式:策略模式(Strategy Pattern)
链接:C 设计模式 链接:C 设计模式 - 模板方法 链接:C 设计模式 - 观察者模式 策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以…...
SpringBoot(Ⅱ-2)——,SpringBoot版本控制,自动装配原理补充(源码),自动导包原理补充(源码),run方法
SpringBoot的版本控制是怎么做的 starter版本控制 SpringBoot的核心父依赖,下面导入的所有starter依赖都不需要指定版本,版本默认和spring-boot-starter-parent的parent版本一致; xxxstarter内部jar包依赖的版本管理,starter自…...
爬虫的工作原理
摘要: 本文详细阐述了爬虫的工作原理,从其基本概念出发,深入探讨了爬虫的主要组成部分,包括URL管理器、网页下载器、网页解析器和数据存储模块等。同时,分析了爬虫的抓取策略,如深度优先、广度优先等&#…...
你了解DNS吗?
你了解DNS吗? 一. 介绍二. DNS的工作原理三. DNS查询流程示意图四. DNS 记录类型五. DNS的安全问题与 DNSSEC 前言 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 一. 介绍 …...
利用JavaScript实现顺序九宫格抽奖
顺序九宫格思路: 1.先获取抽奖按钮,方便给按钮绑定点击事件2.初始化下标k0,用于表示当前选中的索引下标,后续滚动起来会一直刷新3.获取大div盒子4.获取盒子里所有div元素,充当一个数组,后续可以通过下标来访问每个小div࿰…...
音视频入门知识(四):封装篇
⭐四、封装篇 H264封装成mp4、flv等格式,那为什么需要封装? h264也能播放,但是按照帧率进行播放,可能不准 ★FLV **FLV(Flash Video)**是一种用于传输和播放视频的容器文件格式。FLV 格式广泛应用于流媒…...
在基于IMX6ULL的Linux嵌入式编程中,与内存相关的堆(Heap)和栈(Stack)有什么区别?Linux 系统中堆和栈的内存布局是怎么样的?
堆(Heap)和栈(Stack)的概念和区别 在基于 IMX6ULL 的 Linux 嵌入式编程中,堆(Heap)和栈(Stack)是两种不同的内存分配方式,各自具有不同的特点和用途。以下是它们的主要区别: 1. 存储位置 堆&am…...
Sealos Devbox 基础教程:使用 Cursor 从零开发一个 One API 替代品
随着技术的成熟和 AI 的崛起,很多原本需要团队协作才能完成的工作现在都可以通过自动化和智能化的方式完成。于是乎,单个开发者的能力得到了极大的提升 - 借助各种工具,一个人就可以完成开发、测试、运维等整条链路上的工作,渡劫飞…...
pthread.h互斥锁与原子操作
一:互斥锁 pthread.h 是 POSIX 线程库的头文件,它提供了多线程编程所需的各种功能。其中,互斥锁(mutex)的实现涉及多个底层机制: 1. 互斥锁的基本结构 在 POSIX 线程库中,互斥锁通常包含以下…...
网络基础入门到深入(3):网络协议-HTTP/S
目录 一、HTTP和HTTPS协议简介 1.HTTP协议 .HTTP 协议 作用: 特点: 2.HTTPS协议 作用: 实现方式: 特点: 二.HTTP的请求与响应结构 1.HTTP请求结构 1.请求行:描述操作和资源 2.请求头: 3.请求体 : 2.HTTP…...
Git的.gitignore文件详解与常见用法
诸神缄默不语-个人CSDN博文目录 在日常使用 Git 进行版本控制时,我们经常会遇到一些不需要被提交到远程仓库的文件(例如日志文件、临时配置文件、环境变量文件等)。为了忽略这些文件的提交,Git 提供了一个非常有用的功能…...
UniApp 组件的深度运用
一、引言 在当今的移动应用开发领域,跨平台开发已成为主流趋势,而 UniApp 作为其中的佼佼者,备受开发者青睐。UniApp 的强大之处很大程度上源于其丰富且功能多样的组件体系,这些组件宛如精巧的积木,能够帮助开发者快速…...
k8s部署nginx+sshd实现文件上传下载
要通过 nginx 和 sshd 实现文件的上传和下载,通常的做法是结合 SSH 协议和 HTTP 协议,使用 nginx 提供 Web 服务器功能,同时使用 sshd(即 SSH 服务)来处理通过 SSH 协议进行的文件传输。 SSH 实现文件的上传和下载&…...
Spring-Mybatis 2.0
前言: 第一点:过于依赖代码生成器或AI,导致基于mybaits的CRUD通通忘了,所以为了找回遗忘的记忆,有了该系列内容。 第二点:通过实践而发现真理,又通过实践而证实真理和发展真理。从感性认识而能…...
Linux 的历史与发展:从诞生到未来
Linux 的历史与发展:从诞生到未来 1. 起源之前:操作系统的历史背景 在 Linux 问世之前,操作系统的发展经历了多个重要阶段,这些阶段为 Linux 的诞生奠定了基础: 1940-1950 年代:计算机初期 早期计算机如 [[…...
SQL Server实现将分组的其他字段数据拼接成一条数据
在 SQL Server 中,可以使用 STRING_AGG 函数(SQL Server 2017 及更高版本支持)将分组的其他字段数据拼接成一条数据。以下是示例代码: 假设有一个表 Orders,结构如下: OrderIDCustomerIDProduct1C001Appl…...
学习笔记 --C#基础其他知识点(同步和异步)
C#中的同步和异步《一》 以下理解借鉴博客:借鉴博客地址1 异步编程(Asynchronous) 允许任务在后台执行,而不会阻塞调用线程。C#使用async和await关键字 async Task AsynchronousMethod() {// 等待异步操作完成await Task.Dela…...
一维、线性卡尔曼滤波的例程(MATLAB)
这段 MATLAB 代码实现了一维线性卡尔曼滤波器的基本功能,用于估计在存在噪声的情况下目标状态的真实值 文章目录 一维线性卡尔曼滤波代码运行代码介绍1. **初始化部分**2. **数据生成**3. **卡尔曼滤波器实现**4. **结果可视化**5. **统计输出**源代码总结一维线性卡尔曼滤波 …...
极品飞车6的游戏手柄设置
极品飞车,既可以用键盘来控制车辆的前进、后退、左转、右转、加速与减速,也可以使用游戏手柄来操作车辆的运行。需要注意的是,极品飞车虽然支持手柄,但是仅支持常见的北通、罗技还有部分Xbox系列的手柄,至于其他的PS4手…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
