C++线程异步

std::future
std::future作为异步结果的传输通道,可以很方便地获取线程函数的返回值。
 std::future_status
  1. Ready (std::future_status::ready):

    • 当与 std::future 对象关联的异步操作已经完成时,std::future 处于 ready 状态。
    • 在这个状态下,调用 get()wait() 或 wait_for() 将立即返回,并且 get() 将返回操作的结果(或者抛出异常,如果操作以异常结束)。   
  2. Timeout (std::future_status::timeout):

    • 当调用 wait_for() 或 wait_until() 并且指定的等待时间已经过去,但异步操作尚未完成时,std::future 处于 timeout 状态。
    • 这意味着 wait_for() 或 wait_until() 调用返回了 std::future_status::timeout,表明等待超时,但 std::future 仍然可能在未来某个时间点变为 ready 状态。
  3. Deferred (std::future_status::deferred):

    • 当与 std::future 对象关联的异步操作是延迟执行的(即,它将在调用 get()wait()时才执行),std::future 处于 deferred 状态。
    • 这种情况发生在使用 std::async 时指定了 std::launch::deferred,这意味着操作将在第一次调用 get()wait() 或 wait_for() 时在当前线程上执行。 

状态转换图:

std::future 对象在其生命周期内只能从 deferred 状态转移到 ready 状态,或者从 ready 状态转移到 timeout 状态(仅在调用 wait_for() 或 wait_until() 时)。一旦 std::future 对象处于 ready 状态,它将保持这种状态直到其结果被获取或对象被销毁。如果异步操作在调用 wait_for() 或 wait_until() 之前完成,那么即使等待超时,std::future 对象也会处于 ready 状态。  

std::future::get() 

 std::future::wait

当使用 std::asyncstd::packaged_task 或 std::promise 创建异步任务时,你可以通过 std::future 对象来获取结果。std::future 提供了 wait 函数,用于阻塞当前线程,直到与 future 对象关联的共享状态变为就绪状态。

std::future::wait 的行为与 std::future::get 类似,但 wait 不会返回结果,它只是等待操作完成。如果你只是想确保操作完成而不需要立即获取结果,使用 wait 是合适的。 

需要注意的是,如果你在一个已经就绪的 future 上调用 wait,它将立即返回,而不会阻塞。如果你在一个处于 deferred 状态的 future 上调用 wait,它将执行关联的任务并等待其完成。 

#include <future>
#include <iostream>int main() {std::future<int> fut = std::async(std::launch::async, []() {// 异步执行一些操作return 42;});// 等待 future 完成fut.wait();// 获取结果std::cout << "Result: " << fut.get() << std::endl;return 0;
}
std::future::wait_for

它允许你等待一个 future 对象指定的时长。如果 future 在这段时间内变为就绪状态,wait_for 将返回 std::future_status,表明 future 的状态。

测试1: 
#include <future>
#include <iostream>
#include <chrono>int main() {// 创建一个异步任务std::future<int> fut = std::async(std::launch::async, []() {std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟耗时操作return 42;});// 等待 future 完成,最多等待 3 秒auto status = fut.wait_for(std::chrono::seconds(3));if (status == std::future_status::ready) {std::cout << "Future is ready. Result: " << fut.get() << std::endl;} else if (status == std::future_status::timeout) {std::cout << "Future is not ready within the given time." << std::endl;} else if (status == std::future_status::deferred) {std::cout << "Future is deferred and will execute in the calling thread." << std::endl;}return 0;
}

 Future is not ready within the given time.

 在这个例子中,我们创建了一个异步任务,它将在 5 秒后返回结果。我们使用 wait_for 来等待 future 完成,但只等待 3 秒。因此,wait_for 将返回 std::future_status::timeout,表明 future 在指定的 3 秒内没有准备好。

如果我们将 wait_for 的等待时间增加到超过 5 秒,那么 wait_for 将返回 std::future_status::ready,因为异步任务将在等待时间结束前完成。

测试2: 

我自己跑了下,改成5s还是不行:

测试3:

6s可以

 如果任务是以延迟方式执行的(即 std::launch::deferred),那么 wait_for 将返回 std::future_status::deferred。 

------------- 

线程异步操作函数async

std::async可以用来直接创建异步的task,异步任务返回的结果保存在future中。

(1)需要获取异步任务的结果时,调用future.get()方法即可。

(2)不关注异步任务的结果,只是简单地等待任务完成的话,调用future.wait()方法。

std::async的第一个参数: std::launch::async | std::launch::deferred

 std::launch::async :在调用async时就开始创建线程

 std::launch::deferred :延迟加载方式创建线程。调用async时不创建线程,

 直到调用了 future的get或者wait时才创建线程。

 std::async的第二个参数:线程函数

 std::async的第三个参数:线程函数的参数

示例: 

#include <future>
#include <iostream>int main() {// 使用 std::async 创建一个异步任务std::future<int> fut = std::async(std::launch::async, []() {// 异步任务执行一些计算并返回结果return 42;});// 在这里,主线程可能会执行其他任务...// 获取异步任务的结果int result = fut.get(); // 这将阻塞,直到异步任务完成std::cout << "The result is " << result << std::endl;return 0;
}
launch:启动(计算机程序)
sync:同时,同步;协调,一致
std::promise

std::promise将数据与future绑定起来,为获取线程函数中的某个值提供便利,在线程函数中为外面传过来的promise赋值,在线程函数执行完成之后就可以通过promise的future获取该值了。

取值是通过promise内部提供的future来获取的。

promise:承诺

#include <future>
#include <iostream>
#include <chrono>int main() {std::promise<int> pr;std::thread t([](std::promise<int>& p) {// 执行一些操作...p.set_value_at_thread_exit(9); // 在线程退出时设置值}, std::ref(pr));std::future<int> f = pr.get_future();// 等待子线程完成t.join();// 现在可以安全地获取值auto r = f.get();std::cout << "Received: " << r << std::endl;return 0;
}

Received: 9

set_value_at_thread_exit

它允许你设置一个值,这个值将在当前线程退出时传递给与之关联的 std::future 对象

在当前线程退出时,应该将给定的值传递给 std::future。这可以确保即使线程在设置值后立即退出,相关的值也会被存储,并且可以在其他地方通过 std::future 对象获取。

  • 它在 std::promise 类中声明。
  • 它接受一个值,这个值可以是任何可复制的类型。
  • 它不会立即设置值,而是在当前线程退出时设置值。
  • 调用 set_value_at_thread_exit 后,std::promise 对象处于就绪状态,即 future::valid() 为 true
  • 一旦 set_value_at_thread_exit 被调用,你就不能再通过这个 std::promise 对象设置另一个值。

“任何可复制的类型” 指的是那些可以被复制构造函数或复制赋值运算符安全复制的类型。这意味着这些类型的对象可以被创建为其他同类对象的副本,而不会导致未定义行为。 

std::ref

 std::ref 用于创建一个包装器,这个包装器能够将引用传递给接受值传递的函数。这在需要将引用传递给像线程函数或函数对象这样的参数时非常有用。

以下是一些使用 std::ref 的例子: 

1. 将引用传递给线程函数

通常情况下,当创建一个线程时,你传递给 std::thread 构造函数的参数是通过值传递的。如果你想要传递一个引用,你可以使用 std::ref。

#include <iostream>
#include <thread>
#include <functional>void func(int& n) {n++;
}int main() {int n = 0;std::thread t(func, std::ref(n));t.join();std::cout << "n = " << n << std::endl; // 输出 n = 1return 0;
}

在上面的例子中,std::ref(n) 创建了一个引用包装器,使得 func 函数能够通过引用修改 n

--------- 

2. 将引用传递给函数对象

std::ref 也可以用于将引用传递给函数对象。

#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>struct Increment {void operator()(int& n) {n++;}
};int main() {std::vector<int> vec = {1, 2, 3, 4, 5};std::for_each(vec.begin(), vec.end(), Increment());std::for_each(vec.begin(), vec.end(), Increment());for (int n : vec) {std::cout << n << ' '; // 输出 3 4 5 6 7}std::cout << std::endl;return 0;
}

std::for_each 接受一个函数对象 Increment,并且因为 Increment 接受一个引用参数,所以不需要使用 std::ref。但是,如果函数对象是通过值传递参数的,使用 std::ref 可以确保传递的是引用。 

---------- 

记住,std::ref 只在需要引用语义时使用。如果你不需要修改原始对象,或者函数接受的是按值传递的参数,那么使用 std::ref 是不必要的。 

--------- 

std::for_each

 std::for_each 是 C++ 标准库中的一个算法,它用于对容器中的每个元素执行一个操作。这个算法会遍历指定范围内的所有元素,并对每个元素调用提供的函数或函数对象。

 template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f ); 

unary:一元的 

  • InputIt first, InputIt last: 这两个参数定义了要遍历的元素范围。通常,它们是容器的迭代器。
  • UnaryFunction f: 这是一个函数或函数对象,它将被应用于范围内的每个元素。这个函数或函数对象应该接受一个参数,对应于范围内的每个元素.

由于我们正在修改容器中的元素,迭代器必须是能够修改元素的类型,即随机访问迭代器。在 std::vector 中,begin() 和 end() 返回的迭代器就是这种类型。 

 std::for_each 是 C++ 标准库中的一个算法,它用于对容器中的每个元素执行一个操作。这个算法会遍历指定范围内的所有元素,并对每个元素调用提供的函数或函数对象。

 template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f ); 

unary:一元的 

  • InputIt first, InputIt last: 这两个参数定义了要遍历的元素范围。通常,它们是容器的迭代器。
  • UnaryFunction f: 这是一个函数或函数对象,它将被应用于范围内的每个元素。这个函数或函数对象应该接受一个参数,对应于范围内的每个元素.

由于我们正在修改容器中的元素,迭代器必须是能够修改元素的类型,即随机访问迭代器。在 std::vector 中,begin() 和 end() 返回的迭代器就是这种类型。 

----------

传递给 std::thread 构造函数的参数默认是通过值传递的

 当创建一个 std::thread 对象时,传递给 std::thread 构造函数的参数默认是通过值传递的。这意味着,如果传递的是非引用类型,那么线程函数将会接收到传递参数的一个副本。

#include <iostream>
#include <thread>void threadFunction(int n) {// 这里我们接收到的是 n 的副本n += 100;std::cout << "Inside thread: " << n << std::endl;
}int main() {int n = 1;std::thread t(threadFunction, n); // 传递 n 的副本t.join();std::cout << "Outside thread: " << n << std::endl; // n 仍然是 1return 0;
}

 如果我们想要在线程函数中修改原始变量,就需要传递一个引用。但是,由于 std::thread 构造函数默认是按值传递的,因此需要使用 std::ref 来传递引用

#include <iostream>
#include <thread>
#include <functional>void threadFunction(int& n) {n += 100;std::cout << "Inside thread: " << n << std::endl;
}int main() {int n = 1;std::thread t(threadFunction, std::ref(n)); // 使用 std::ref 传递引用t.join();std::cout << "Outside thread: " << n << std::endl; // n 现在是 101return 0;
}

 这就是 std::ref 的用途所在,它允许在线程或其他按值传递参数的上下文中传递引用。

---------

线程退出

线程的退出时机取决于线程的执行逻辑和线程的创建方式。以下是几种情况下线程可能退出的时机:

  1. 任务完成退出:线程执行的任务完成后,线程函数(无论是普通函数、成员函数还是 lambda 表达式)会执行返回语句。当线程函数返回时,线程会自然退出。这是线程退出的最常见方式。

  2. 自毁退出:在 C++ 中,当 std::thread 对象被销毁时,如果关联的线程仍在运行,std::thread 的析构函数会调用 std::terminate 来终止程序,除非:

    • 线程已经完成了它的执行(任务完成)。
    • 线程是通过 std::detach 被分离的。
    • 线程被 join 过,且 std::thread 对象是通过移动构造或移动赋值创建的。
  3. 分离退出:如果线程被分离(使用 std::thread::detach),它将在后台运行,与创建它的 std::thread 对象无关。线程将在执行完毕后自动退出,资源被系统回收。

  4. 异常退出:如果线程执行的函数抛出了未捕获的异常,并且没有设置相应的异常处理机制,线程将异常退出。

  5. 外部干预退出:线程可以被外部干预强制退出,例如:

    • 调用 pthread_cancel(在 POSIX 线程中)来请求取消线程。
    • 在 Windows 中,可以通过调用 TerminateThread 或 ExitThread 来终止线程,但这通常是不安全的做法,因为它可能导致资源泄露和其他问题。
  6. 程序终止:当主线程退出时,如果其他线程是可连接的(joinable),程序通常会调用 std::terminate 来终止这些线程。如果其他线程是分离的,它们可能在主线程退出后继续运行,直到完成或被系统强制终止。

为了确保资源得到正确释放,线程应当尽可能通过自然的退出路径结束,例如任务完成或通过 join 等待线程结束。避免使用强制退出线程的方法,因为这可能会导致不一致的状态和资源泄露。

  1. 移动构造和移动赋值:当一个 std::thread 对象是通过移动构造或移动赋值创建的,它会接手源对象所管理的线程,而源对象将不再管理任何线程(即变为非.joinable状态)。

  2. 被 join 过的 std::thread:如果一个 std::thread 对象已经调用了 join,那么它将不再管理任何线程,也就是说它变成了非.joinable状态。在这种情况下,再次调用 join 是不允许的,将会导致未定义行为。

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

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

相关文章

阿里云k8s-master部署CNI网络插件遇到的问题

问题 按照网络上的部署方法 cd /opt/k8s # 下载 calico-kube-controllers配置文件&#xff0c;可能会网络超时 curl https://docs.projectcalico.org/manifests/calico.yaml -O kubectl apply -f calico.yaml 试了很多次都不行&#xff0c;k8s-master都是Not ready的状态 ca…

从壹开始解读Yolov11【源码研读系列】——Data.Base.py.BaseDataset:可灵活改写的数据集加载处理基类

目录 一、base.BaseDataset 1.__init__类初始化 2.get_img_files根据地址获得图片详细地址 3.get_labels&#xff08;自定义&#xff09;获取标签数据 4. update_labels指定类别和单分类设定 5.set_rectangle开启批量矩阵训练 6.cache_images加载图片进程可视化 7.load_image内…

计算机毕业设计Hadoop+大模型地震预测系统 地震数据分析可视化 地震爬虫 大数据毕业设计 Spark 机器学习 深度学习 Flink 大数据

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

数据结构和算法(六):贪心算法、分治算法、回溯算法、动态规划、拓扑排序

从广义上来讲&#xff1a;数据结构就是一组数据的存储结构 &#xff0c; 算法就是操作数据的方法 数据结构是为算法服务的&#xff0c;算法是要作用在特定的数据结构上的。 10个最常用的数据结构&#xff1a;数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie树 10个最…

顺序表和链表(一)

目录 线性表 一、顺序表 <1>顺序表 &#xff08;1&#xff09;静态顺序表 &#xff08;2&#xff09;动态顺序表-按需申请 <2>链表 &#xff08;1&#xff09;单链表 &#xff08;2&#xff09;双链表 主程序&#xff08;test.c&#xff09; 头文件&#…

# Ubuntu 达人九步养成记(1)

Ubuntu 达人九步养成记&#xff08;1&#xff09; 目录&#xff1a; 一、ubuntu基本安装 二、设置语言环境 三、设置服务器镜像源 四、在启动栏添加终端图标 五、使用apt更新和升级系统软件 六、使用apt安装软件 七、使用apt删除软件以及apt-get 八、deb格式及谷歌浏览…

QT——TCP网络调试助手

目录 一.项目展示 ​编辑 二.开发流程 三.QTcpServer、QTcpSocket、QUdpSocket类的学习 1.QTcpServer服务端 2.QTcpSocket客户端 3.Udp通信 四.网络调试助手 1.首先我们实现当用户选择不同协议类型时不同的UI组件如何切换 2.实现打开/关闭按键图片的切换 方式一&…

导航栏渐变色iOS

- (void)viewDidLoad {[super viewDidLoad];// 设置导航栏属性self.navigationBar.translucent NO;[self.navigationBar setTitleTextAttributes:{NSForegroundColorAttributeName : [UIColor whiteColor], NSFontAttributeName:[UIFont boldSystemFontOfSize:28]}];// 修复iO…

《Web性能权威指南》-浏览器API与协议-读书笔记

本文是《Web性能权威指南》第四部分——浏览器API与协议的读书笔记。 第一部分——网络技术概览&#xff0c;请参考网络技术概览&#xff1b; 第二部分——无线网络性能&#xff0c;请参考无线网络性能&#xff1b; 第三部分——HTTP&#xff0c;请参考HTTP。 浏览器网络概述 …

使用TypeORM进行数据库操作

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用TypeORM进行数据库操作 引言 TypeORM 简介 安装 TypeORM 配置 TypeORM 定义实体 连接数据库 运行项目 高级功能 事务管理 关…

ESP-HaloPanel:用 ESP32-C2 打造超低成本智能家居面板

项目简介 在生活品质日益提升的今天&#xff0c;智能家居系统已经走进了千家万户&#xff0c;并逐渐成为现代生活的一部份。与此同时&#xff0c;一款设计精致、体积轻盈、操作简便的全屋智能家居控制面板&#xff0c;已经成为众多家庭的新宠。这种高效、直观的智能化的解决方…

Hadoop生态圈框架部署(四)- Hadoop完全分布式部署

文章目录 前言一、Hadoop完全分布式部署&#xff08;手动部署&#xff09;1. 下载hadoop2. 上传安装包2. 解压hadoop安装包3. 配置hadoop配置文件3.1 虚拟机hadoop1修改hadoop配置文件3.1.1 修改 hadoop-env.sh 配置文件3.3.2 修改 core-site.xml 配置文件3.3.3 修改 hdfs-site…

数据建模圣经|数据模型资源手册卷3,数据建模最佳实践

简介 本书采用了类设计模式的方式对数据模型进行高度抽象总结&#xff0c;展现了常见的数据模型构建模型等模型的作用、层次、分类、地位、沟通方式&#xff0c;和业务规则。使用一个强大的数据模型模式的数据建模&#xff0c;评估特定与广义模型的优缺点&#xff0c;有助于你改…

【力扣】Go语言回溯算法详细实现与方法论提炼

文章目录 一、引言二、回溯算法的核心概念三、组合问题1. LeetCode 77. 组合2. LeetCode 216. 组合总和III3. LeetCode 17. 电话号码的字母组合4. LeetCode 39. 组合总和5. LeetCode 40. 组合总和 II小结 四、分割问题6. LeetCode 131. 分割回文串7. LeetCode 93. 复原IP地址小…

#渗透测试#SRC漏洞挖掘# 信息收集-Shodan进阶之Mongodb未授权访问

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

Golang--流程控制

1、分支结构 1.1 if分支 单分支 语法&#xff1a;if 条件表达式 { 逻辑代码 } 当条件表达式为true时&#xff0c;就会执行代码块的代码。条件表达式左右的()可以不写&#xff0c;也建议不写 if和表达式中间&#xff0c;一定要有空格在Golang中&#xff0c;{}是必须有的,就算你…

【补补漏洞吧 | 02】等保测评ZooKeeperElasticsearch未授权访问漏洞补漏方法

一、项目背景 客户新系统上线&#xff0c;因为行业网络安全要求&#xff0c;需要做等保测评&#xff0c; 通过第三方漏扫工具扫描系统&#xff0c;漏扫报告显示ZooKeeper和 Elasticsearch 服务各拥有一个漏洞&#xff0c;具体结果如下&#xff1a; 1、ZooKeeper 未授权访问【…

Serverless + AI 让应用开发更简单

本文整理自 2024 云栖大会&#xff0c;阿里云智能高级技术专家&#xff0c;史明伟演讲议题《Serverless AI 让应用开发更简单》 随着云计算和人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;企业对于高效、灵活且成本效益高的解决方案的需求日益增长。本文旨在…

从0开始学PHP面向对象内容之(类,对象,构造/析构函数)

上期我们讲了面向对象的一些基本信息&#xff0c;这期让我们详细的了解一下 一、面向对象—类 1、PHP类的定义语法&#xff1a; <?php class className {var $var1;var $var2 "constant string";function classfunc ($arg1, $arg2) {[..]}[..] } ?>2、解…

(八)JavaWeb后端开发——Tomcat

目录 1.Web服务器概念 2.tomcat 1.Web服务器概念 服务器&#xff1a;安装了服务器软件的计算机服务器软件&#xff1a;接收用户的请求&#xff0c;处理请求&#xff0c;做出响应web服务器软件&#xff1a;在web服务器软件中&#xff0c;可以部署web项目&#xff0c;让用户通…