当前位置: 首页 > news >正文

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程序都含有至少一个线程&#xff0c;即运行main()的线程&#xff0c;它由C运行时系统启动。随后程序可以发起更多线程&#xff0c;它们以别的函数作为入口。这些新线程连同起始线程并发运行。当main()返回时&#xff0c;程序就会退出&#xff1b;同样&…...

C++ 设计模式:策略模式(Strategy Pattern)

链接&#xff1a;C 设计模式 链接&#xff1a;C 设计模式 - 模板方法 链接&#xff1a;C 设计模式 - 观察者模式 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以…...

SpringBoot(Ⅱ-2)——,SpringBoot版本控制,自动装配原理补充(源码),自动导包原理补充(源码),run方法

SpringBoot的版本控制是怎么做的 starter版本控制 SpringBoot的核心父依赖&#xff0c;下面导入的所有starter依赖都不需要指定版本&#xff0c;版本默认和spring-boot-starter-parent的parent版本一致&#xff1b; xxxstarter内部jar包依赖的版本管理&#xff0c;starter自…...

爬虫的工作原理

摘要&#xff1a; 本文详细阐述了爬虫的工作原理&#xff0c;从其基本概念出发&#xff0c;深入探讨了爬虫的主要组成部分&#xff0c;包括URL管理器、网页下载器、网页解析器和数据存储模块等。同时&#xff0c;分析了爬虫的抓取策略&#xff0c;如深度优先、广度优先等&#…...

你了解DNS吗?

你了解DNS吗&#xff1f; 一. 介绍二. DNS的工作原理三. DNS查询流程示意图四. DNS 记录类型五. DNS的安全问题与 DNSSEC 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 一. 介绍 …...

利用JavaScript实现顺序九宫格抽奖

顺序九宫格思路&#xff1a; 1.先获取抽奖按钮,方便给按钮绑定点击事件2.初始化下标k0,用于表示当前选中的索引下标&#xff0c;后续滚动起来会一直刷新3.获取大div盒子4.获取盒子里所有div元素&#xff0c;充当一个数组&#xff0c;后续可以通过下标来访问每个小div&#xff0…...

音视频入门知识(四):封装篇

⭐四、封装篇 H264封装成mp4、flv等格式&#xff0c;那为什么需要封装&#xff1f; ​ h264也能播放&#xff0c;但是按照帧率进行播放&#xff0c;可能不准 ★FLV **FLV&#xff08;Flash Video&#xff09;**是一种用于传输和播放视频的容器文件格式。FLV 格式广泛应用于流媒…...

在基于IMX6ULL的Linux嵌入式编程中,与内存相关的堆(Heap)和栈(Stack)有什么区别?Linux 系统中堆和栈的内存布局是怎么样的?

堆(Heap)和栈(Stack)的概念和区别 在基于 IMX6ULL 的 Linux 嵌入式编程中&#xff0c;堆&#xff08;Heap&#xff09;和栈&#xff08;Stack&#xff09;是两种不同的内存分配方式&#xff0c;各自具有不同的特点和用途。以下是它们的主要区别&#xff1a; 1. 存储位置 堆&am…...

Sealos Devbox 基础教程:使用 Cursor 从零开发一个 One API 替代品

随着技术的成熟和 AI 的崛起&#xff0c;很多原本需要团队协作才能完成的工作现在都可以通过自动化和智能化的方式完成。于是乎&#xff0c;单个开发者的能力得到了极大的提升 - 借助各种工具&#xff0c;一个人就可以完成开发、测试、运维等整条链路上的工作&#xff0c;渡劫飞…...

pthread.h互斥锁与原子操作

一&#xff1a;互斥锁 pthread.h 是 POSIX 线程库的头文件&#xff0c;它提供了多线程编程所需的各种功能。其中&#xff0c;互斥锁&#xff08;mutex&#xff09;的实现涉及多个底层机制&#xff1a; 1. 互斥锁的基本结构 在 POSIX 线程库中&#xff0c;互斥锁通常包含以下…...

网络基础入门到深入(3):网络协议-HTTP/S

目录 一、HTTP和HTTPS协议简介 1.HTTP协议 .HTTP 协议 作用&#xff1a; 特点&#xff1a; 2.HTTPS协议 作用&#xff1a; 实现方式&#xff1a; 特点&#xff1a; 二.HTTP的请求与响应结构 1.HTTP请求结构 1.请求行:描述操作和资源 2.请求头: 3.请求体 : 2.HTTP…...

Git的.gitignore文件详解与常见用法

诸神缄默不语-个人CSDN博文目录 在日常使用 Git 进行版本控制时&#xff0c;我们经常会遇到一些不需要被提交到远程仓库的文件&#xff08;例如日志文件、临时配置文件、环境变量文件等&#xff09;。为了忽略这些文件的提交&#xff0c;Git 提供了一个非常有用的功能&#xf…...

UniApp 组件的深度运用

一、引言 在当今的移动应用开发领域&#xff0c;跨平台开发已成为主流趋势&#xff0c;而 UniApp 作为其中的佼佼者&#xff0c;备受开发者青睐。UniApp 的强大之处很大程度上源于其丰富且功能多样的组件体系&#xff0c;这些组件宛如精巧的积木&#xff0c;能够帮助开发者快速…...

k8s部署nginx+sshd实现文件上传下载

要通过 nginx 和 sshd 实现文件的上传和下载&#xff0c;通常的做法是结合 SSH 协议和 HTTP 协议&#xff0c;使用 nginx 提供 Web 服务器功能&#xff0c;同时使用 sshd&#xff08;即 SSH 服务&#xff09;来处理通过 SSH 协议进行的文件传输。 SSH 实现文件的上传和下载&…...

Spring-Mybatis 2.0

前言&#xff1a; 第一点&#xff1a;过于依赖代码生成器或AI&#xff0c;导致基于mybaits的CRUD通通忘了&#xff0c;所以为了找回遗忘的记忆&#xff0c;有了该系列内容。 第二点&#xff1a;通过实践而发现真理&#xff0c;又通过实践而证实真理和发展真理。从感性认识而能…...

Linux 的历史与发展:从诞生到未来

Linux 的历史与发展&#xff1a;从诞生到未来 1. 起源之前&#xff1a;操作系统的历史背景 在 Linux 问世之前&#xff0c;操作系统的发展经历了多个重要阶段&#xff0c;这些阶段为 Linux 的诞生奠定了基础&#xff1a; 1940-1950 年代&#xff1a;计算机初期 早期计算机如 [[…...

SQL Server实现将分组的其他字段数据拼接成一条数据

在 SQL Server 中&#xff0c;可以使用 STRING_AGG 函数&#xff08;SQL Server 2017 及更高版本支持&#xff09;将分组的其他字段数据拼接成一条数据。以下是示例代码&#xff1a; 假设有一个表 Orders&#xff0c;结构如下&#xff1a; OrderIDCustomerIDProduct1C001Appl…...

学习笔记 --C#基础其他知识点(同步和异步)

C#中的同步和异步《一》 以下理解借鉴博客&#xff1a;借鉴博客地址1 异步编程&#xff08;Asynchronous&#xff09; 允许任务在后台执行&#xff0c;而不会阻塞调用线程。C#使用async和await关键字 async Task AsynchronousMethod() {// 等待异步操作完成await Task.Dela…...

一维、线性卡尔曼滤波的例程(MATLAB)

这段 MATLAB 代码实现了一维线性卡尔曼滤波器的基本功能,用于估计在存在噪声的情况下目标状态的真实值 文章目录 一维线性卡尔曼滤波代码运行代码介绍1. **初始化部分**2. **数据生成**3. **卡尔曼滤波器实现**4. **结果可视化**5. **统计输出**源代码总结一维线性卡尔曼滤波 …...

极品飞车6的游戏手柄设置

极品飞车&#xff0c;既可以用键盘来控制车辆的前进、后退、左转、右转、加速与减速&#xff0c;也可以使用游戏手柄来操作车辆的运行。需要注意的是&#xff0c;极品飞车虽然支持手柄&#xff0c;但是仅支持常见的北通、罗技还有部分Xbox系列的手柄&#xff0c;至于其他的PS4手…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...