[C++并发编程] 线程基础

线程发起

最简单的发起一个线程。

void thread_work(std::string str) {std::cout << "str: " << std << std::endl; 
}
//初始化并启动一个线程
std::thread t1(thread, wangzn2016);

线程等待:

线程发起后,可能新的线程还没立即执行,或者还没执行完成,主线程就执行完了,此时子线程就会被回收,回收时会调用线程的析构函数,执行terminate操作。

因此,为了防止主线程退出或者局部作用域结束导致子线程被回收析构,我们可以通过join的方式,让主线程等待子线程执行完成并回收。

void thread_work(std::string str) {std::cout << "str: " << std << std::endl; 
}
//初始化并启动一个线程
std::thread t1(thread, wangzn2016);
//主线程等待子线程执行完成
t1.join();

仿函数作为参数

也可以使用仿函数作为参数发起线程

class background_task {
public:void operator()() {std::cout << "wangzn2016" << str << std::endl;}
};
//这种方式会被编译器识别成函数"std::stread (*)(background_task) (*)()"
//std::thread t1(background_task());//其中一个解决办法就是,实例化一个对象后,再传入
//这里提供另外两种种解决办法
// 1.多加一层()
std::thread t2((background_task()));
t2.join();// 2.使用{}来初始化
std::thread t3{ background_task() };
t3.join();

Lambda表达式

lambda表达式也可以作为线程的参数传递给thread。

std::thread t1([](std::string  str) {std::cout << "str: " << str << std::endl;
},  hellostr);t1.join();

线程detach

线程分离,也可以被称为守护线程。线程允许采用分离的方式在后台独自运行。

struct func {int& _i;func(int& i): _i(i){}void operator()() {for (int i = 0; i < 3; i++) {_i = i;std::cout << "_i: " << _i << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}}
};void oops() {int some_local_state = 0;func myfunc(some_local_state);std::thread functhread(myfunc);//隐患,访问局部变量,局部变量可能会随着}结束而回收或随着主线程退出而回收functhread.detach();    
}// detach 注意事项
oops();
//防止主线程退出过快,需要停顿一下,让子线程跑起来detach
std::this_thread::sleep_for(std::chrono::seconds(1));

上面的例子存在隐患,因为some_local_state是局部变量, 当oops调用结束后局部变量some_local_state就可能被释放了,而线程还在detach后台运行,容易出现崩溃。

解决办法:

  • 通过智能指针传递参数,因为引用计数会随着赋值增加,可保证局部变量在使用期间不被释放,这也就是我们之前提到的伪闭包策略。
  • 将局部变量的值作为参数传递,这么做需要局部变量有拷贝复制的功能,而且拷贝耗费空间和效率。
  • 将线程运行的方式修改为join,这样能保证局部变量被释放前线程已经运行结束。但是这么做可能会影响运行逻辑。 比如下面的修改
void use_join() 
{ int some_local_state = 0; func myfunc(some_local_state); std::thread functhread(myfunc); functhread.join(); 
}      

异常处理

void catch_exception() {int some_local_state = 0;func myfunc(some_local_state);std::thread  functhread{ myfunc };try {//本线程做一些事情,可能引发崩溃std::this_thread::sleep_for(std::chrono::seconds(1));}catch (std::exception& e) {functhread.join();throw;}functhread.join();
}

但是用这种方式编码,会显得臃肿,可以采用RAII技术,保证线程对象析构的时候等待线程运行结束,回收资源。

class thread_guard {
private:std::thread& _t;
public:explicit thread_guard(std::thread& t):_t(t){}~thread_guard() {//join只能调用一次if (_t.joinable()) {_t.join();}}thread_guard(thread_guard const&) = delete;thread_guard& operator=(thread_guard const&) = delete;
};void auto_guard() {int some_local_state = 0;func my_func(some_local_state);std::thread t(my_func);thread_guard g(t);//本线程做一些事情std::cout << "auto guard finished " << std::endl;
}auto_guard();

慎用隐式类型转换

c++中会有一些隐式类型转换,比如char*转换为string等。这些隐式类型转换在线程的调用上可能会造成崩溃问题

void print_str(int i, std::string str)
{std::cout << "i: " << i << " std: " << str << std::endl;
}
void danger_oops(int som_param) {char buffer[1024];sprintf(buffer, "%i", som_param);//在线程内部将char const* 转化为std::stringstd::thread t(print_str, 3, buffer);t.detach();std::cout << "danger oops finished " << std::endl;
}

我们定义一个线程变量thread t时,传递给这个线程的参数buffer会被保存到thread的成员变量中。 而在线程对象t内部启动并运行线程时,参数才会被传递给调用函数print_str。 而此时buffer可能随着}运行结束而释放了。

解决的方式很简单,我们将参数传递给thread时显示转换为string就可以了, 这样thread内部保存的是string类型。

void safe_oops(int some_param) {char buffer[1024];sprintf(buffer, "%i", some_param);std::thread t(print_str, 3, std::string(buffer));t.detach();
}

引用参数

当我们创建的线程要调用的回调函数的参数为引用类型的时候,需要将参数显示转化为引用对象传递给线程的构造函数。

void change_param(int& param) {param++;
}void ref_oops(int some_param) {std::cout << "before change , param is " << some_param << std::endl;//需使用引用显示转换, std::refstd::thread  t2(change_param, std::ref(some_param));t2.join();std::cout << "after change , param is " << some_param << std::endl;
}

因为线程的启动在底层是模板实现,并且嵌套多个函数,引用类型在传参的时候涉及到引用降级(引用折叠),最后传递给change_param函数的参数是个右值,因此会报错,所以我们需要使用引用显示转换, std::ref()来传参。

thread原理

template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>
_NODISCARD_CTOR explicit thread(_Fn&& _Fx, _Args&&... _Ax) {_Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
}

thread构造函数内部通过forward原样转换传递给_Start函数。关于原样转换的知识可以看我之前写的文章。 _Start 函数内部就是启动了一个线程_beginthreadex执行回调函数。

template <class _Fn, class... _Args>void _Start(_Fn&& _Fx, _Args&&... _Ax) {using _Tuple                 = tuple<decay_t<_Fn>, decay_t<_Args>...>;auto _Decay_copied           = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});#pragma warning(push)
#pragma warning(disable : 5039) // pointer or reference to potentially throwing function passed to// extern C function under -EHc. Undefined behavior may occur// if this function throws an exception. (/Wall)_Thr._Hnd =reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));
#pragma warning(pop)if (_Thr._Hnd) { // ownership transferred to the thread(void) _Decay_copied.release();} else { // failed to start thread_Thr._Id = 0;_Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);}}

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

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

相关文章

C++ STL 容器系列(三)list —— 编程世界的万能胶,数据结构中的百变精灵

STL系列学习参考&#xff1a; C STL系列__zwy的博客-CSDN博客https://blog.csdn.net/bite_zwy/category_12838593.html 学习C STL的三个境界&#xff0c;会用&#xff0c;明理&#xff0c;能扩展&#xff0c;STL中的所有容器都遵循这个规律&#xff0c;下面我们就按照这三个境…

UE5 打包环境、C++环境安装说明

文章目录 前言一、安装 Visual Studio 及其必装组件1. 下载安装包1.1 方法11.1 方法22. 安装 Visual Studio3. 修改安装位置(可选)4. 勾选必装组件二、打包错误排查1. 错误:Visual Studio 2022 compiler version 14.42.34433 is not a preferred version. Please use the la…

【AI技术赋能有限元分析应用实践】Abaqus有限元分析到深度学习方法应用全过程——汽车刹车片热力耦合分析

目录 一、项目实现介绍**项目背景****项目目标****项目流程概述****技术融合****项目价值** 二、实现流程**Step 1: 分析问题构建方法&#xff0c;寻找主要分析目标&#xff0c;确定初步目标****Step 2: 使用 Abaqus 完成有限元仿真&#xff0c;后处理并保存数据为 odb 格式***…

从0开始linux(38)——线程(1)线程概念

欢迎来到博主专栏&#xff1a;从0开始linux 博主ID&#xff1a;代码小豪 文章目录 进程与线程线程概念线程的优点线程的独立数据 进程与线程 如果要理解线程&#xff0c;那么进程将会时绕不开的点。首先我们回顾一下我们之前在进程章节当中是如何描述进程的&#xff1f; 进程&…

Vue中的计算属性和监听属性

在Vue中&#xff0c;计算属性和监听属性是两种非常有用的功能&#xff0c;它们可以帮助我们更好地管理数据和响应数据的变化。 计算属性 计算属性是基于它们的依赖进行缓存的。只有当依赖发生变化时&#xff0c;计算属性才会重新计算。这使得计算属性非常适合用于执行昂贵的计…

Doge东哥wordpress主题

Doge东哥wordpress主题是一款专为中小型企业设计的WordPress外贸网站模板&#xff0c;它以其现代、专业且用户友好的界面&#xff0c;为企业提供了一个展示产品和服务的理想平台。以下是对该模板的详细描述&#xff1a; 首页设计概览 首页的设计简洁而不失大气&#xff0c;顶…

keil 5. Flash Timeout. Reset the Target and try it again.

使用官方STM32 ST-LINK Utility 烧写软件 KEIL 5, 设置DFP 包支持FLASH烧写算法 Keil 5, Flash Timeout. Reset the Target and try it again.-CSDN博客

MySQL源码编译

华子目录 下载源码包上传并解压安装cmake环境检测make编译make install安装 部署 下载源码包 下载相应源码包mysql5.7编译安装需要boost库&#xff0c;这里官网下载含boost的源码包https://downloads.mysql.com/archives/community/ 上传并解压 [rootmysql-node1 ~]# du -sh …

【Canvas与化学】枣红实心球钙元素图标

【成图】 120*120 大小图 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>钙元素图标 Draft2</title><style type&qu…

YOLOv8实战无人机视角目标检测

本文采用YOLOv8作为核心算法框架&#xff0c;结合PyQt5构建用户界面&#xff0c;使用Python3进行开发。YOLOv8以其高效的实时检测能力&#xff0c;在多个目标检测任务中展现出卓越性能。本研究针对无人机目标数据集进行训练和优化&#xff0c;该数据集包含丰富的无人机目标图像…

C#里怎么样使用new修饰符来让类智能选择基类函数还是派生类函数?

C#里怎么样使用new修饰符来让类智能选择基类函数还是派生类函数&#xff1f; 在C#里有一个特殊的用法&#xff0c;就是在创建与基类相同的函数时&#xff0c; 如果使用一个new修饰符&#xff0c;就会导致它会根据变量的类型而选择不同的函数运行。 这是一个比较特殊的使用方法&…

私有库gitea安装

一 gitea是什么 Gitea是一款自助Git服务&#xff0c;简单来说&#xff0c;就是可以一个私有的github。 搭建很容易。 Gitea依赖于Git。 类似Gitea的还有GitHub、Gitee、GitLab等。 以下是安装步骤。 二 安装sqilite 参考&#xff1a; 在windows上安装sqlite 三 安装git…

netconf 代码架构

NETCONF&#xff08;Network Configuration Protocol&#xff09;是一种基于 XML 的网络配置管理协议&#xff0c;主要用于在网络设备之间进行配置管理、状态监控和操作。它被设计为一种可扩展的协议&#xff0c;并且在自动化网络管理中扮演着重要角色。NETCONF 通过安全的通信…

【Anaconda】 创建环境报错:CondaHTTPError: HTTP 000 CONNECTION FAILED for url

问题描述 使用 Anaconda 创建环境时报错&#xff1a; CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://repo.anaconda.com/pkgs/free/noarch/repodata.json.bz2> Elapsed: -An HTTP error occurred when trying to retrieve this URL. HTTP errors are o…

十一、快速入门go语言之接口和反射

文章目录 接口:one: 接口基础:two: 接口类型断言和空接口:star2: 空接口实现存储不同数据类型的切片/数组:star2: 复制切片到空接口切片:star2: 类型断言 反射 &#x1f4c5; 2024年5月9日 &#x1f4e6; 使用版本为1.21.5 接口 十、Java类的封装和继承、多态 - 七点半的菜市…

计算机的错误计算(一百七十)

摘要 回复一中学生来信&#xff0c;探讨 MATLAB 关于算式 的计算问题。 在计算机的错误计算&#xff08;一百三十二&#xff09;中&#xff0c;我们探讨了手持式计算器关于算式 的计算问题。一中学生来信询问该算式在数学软件中是否会出错。 例1. 在 MATLAB 中计算 . 首…

Maven CMD命令

打包测试命令 在当前文件中 >mvn clean package -D maven.test.skiptrue 基本命令 mvn clean 清理目标目录&#xff08;target&#xff09;中的输出文件。 mvn compile 编译主源代码路径&#xff08;src/main/java&#xff09;下的 Java 代码。 mvn test-compile 编译测试源…

redis升级

服务器原来使用yum安装的。可以参考下面文章 Linux---Redis安装以及配置_yum安装redis-CSDN博客 yum安装的redis版本比较旧&#xff0c;我们直接下载源码编译安装。 我们下载已经发布的版本 Releases redis/redis GitHub 1、解压 2、make 3、make install 4、修改red…

【科研】9如何高效阅读和理解学术论文

【科研】9如何高效阅读和理解学术论文 写在最前面一、为什么需要系统的阅读方法&#xff1f;二、阅读论文的11步方法三、实践示例四、常见问题解答五、结语 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&#xff0c;感谢你的陪伴与支持 ~ …

iptables 用于设置、维护和检查 IP 数据包的过滤规则。其基本用法是通过命令行界面配置流量的过滤策略,分为以下几类规则链:INPUT(入站流量)、OU

iptables 是 Linux 下的一个强大的防火墙工具&#xff0c;用于设置、维护和检查 IP 数据包的过滤规则。其基本用法是通过命令行界面配置流量的过滤策略&#xff0c;分为以下几类规则链&#xff1a;INPUT&#xff08;入站流量&#xff09;、OUTPUT&#xff08;出站流量&#xff…