C++11 线程异步

news2024/9/22 23:33:55

文章目录

    • 1. 线程异步的概念
    • 2. future
      • 2.1 共享状态
      • 2.2 常用成员函数
    • 3. promise
      • 3.1 常用成员函数
      • 3.2 promise的基本使用
    • 4. package_task
      • 4.1 常用成员函数
      • 4.2 package_task的基本使用
    • 5. async
      • 5.1 async的基本使用
    • 6. promise、package_task、async的对比与总结

1. 线程异步的概念

问题1: 如何理解线程异步?

 异步的反义词是同步。异步与同步的区别见此处: [[Linux/计算机网络基础知识点/高级IO#同步通信 vs 异步通信]]。实际上在多线程下,大部分时候都是存在过异步这一状态的。主线程在创建了子线程后,也去干自己的任务了。

问题2: 线程异步的应用场景?

  1. 主线程想要得到某一子线程运行的任务函数的运行结果。这里的结果可以使用future对象进行存储。(future是个模板类, 能存储任意类型, 包括void) //子—>主
  2. 主线程想要通知子线程, 依靠future值和状态 来达成某一目的(让子线程结束/满足条件/…), 此时主线程在外面设置future对象的共享状态及future值。子线程那边可以根据future对象的状态和值进行一些逻辑判断然后到达想要的结果。 //主—>子

2. future

//包含于<future>头文件
template <class T>  future;
template <class R&> future<R&>;     // specialization : T is a reference type (R&)
template <>         future<void>;   // specialization : T is void
//---------------------------------------------------
//构造函数
future() noexcept;					//(1) default
future (const future&) = delete;	//(2) copy [deleted]
future (future&& x) noexcept;		//(3) move

//赋值
future& operator=(future&& other) noexcept;
future& operator=(const future& other) = delete;


 future对象是用于存储某一类型<class T>的值的,只不过这个值往往是在未来才能获取到。 它被用来作为线程异步的中间存储值。future的值由以下三个异步任务的提供者(Provider)提供:

  1. std::promise
  2. std::package_task
  3. std::async

 我们根据future的构造函数可以发现,future不支持拷贝构造。future的operator=()会去调用移动构造。

2.1 共享状态

 future在线程异步当中扮演的是一个被动角色。它需要与promisepackage_taskasync配合来实现线程异步。由于它必须要进行共享关联,因此future对象时存在共享状态是否有效的问题的。只有共享状态有效,才能获取future的值

 future对象是有"共享状态"这一概念的。共享状态必须依靠上面提到的三者对应的方法: promise::get_future()package_task::get_future()async()获取。否则单纯的创建一个future对象, 它的共享状态是无效的!

共享状态解释
future_status::deferred子线程中的任务函仍未启动
future_status::timeout子线程中的任务正在执行中,指定等待时长已用完
future_status::ready子线程中的任务已经执行完毕结果已就绪

 实际上,为了方便我们理解,还应该加一个状态: 无效状态(invalid)。这个状态存在于:
①future对象没有接收任何提供者的共享关联;
②future对象ready完毕后,被调用者通过get()获取过了。

2.2 常用成员函数

成员函数功能
valid()判断共享状态是否有效
wait()等待共享状态ready
wait_for()等待一段时间
wait_until()等待到某个时间点
get()获取future的值

注意:

  • 在调用get()时,如果future的共享状态不是ready, 则调用者会被阻塞。
  • get()只能被调用一次,第二次会抛出异常。(因为第一次完成后,future的状态就是无效的了)
  • 调用wait()方法会阻塞式等待共享状态为ready。
  • wait_for()wait_until()无法保证等待结束后的future对象的状态一定是ready! (所以它们不太常用, 因为调用完毕后还需要使用valid()判断共享状态)
  • wait_for()wait_until()的返回值是std::future_status。因此我们可以通过接收它们的返回值来循环判断future对象是否ready

3. promise

//包含于<future>头文件
template <class T>  promise;
template <class R&> promise<R&>;  // specialization : T is a reference type (R&)
template <>         promise<void>;// specialization : T is void

//构造函数
promise();								//(1)
promise(promise&& other) noexcept;		//(2) 移动构造
promise(const promise& other) = delete;	//(3) 禁止拷贝构造

//赋值
promise& operator= (promise&& rhs) noexcept; //允许移动赋值
promise& operator= (const promise&) = delete;//禁止拷贝赋值

 promise是一个协助线程赋值的类,在promise类的内部管理着一个future对象。因此它能够提供一些将数据和future对象绑定起来的接口。

3.1 常用成员函数

成员函数功能
get_future()获取future对象
set_value()设置future对象的值(立刻)
set_value_at_thread_exit()在线程结束时,才会设置future对象的值,


• get_future()

 get_future()会返回一个future对象, 此时如果去接收它的返回值则会触发移动赋值, 将资源转移。

• set_value()

 设置future对象的值,并立即设置future对象的共享状态为ready

• set_value_at_thread_exit()

 设置future对象的值,但是不会立刻让future对象的共享状态为ready。在子线程退出时,子线程资源被销毁,再令共享状态为ready

3.2 promise的基本使用

①: 子线程 set_value—> 给主线程

  1. 在主线程中创建promise对象
  2. 将这个promise对象通过引用传递的方式传给子线程的任务函数(ref)
  3. 子线程合适的时候调用set_value()方法, 设置future对象的值以及状态(ready)
  4. 主线程通过调用promise对象中的get_future()方法获取到future对象 (这里是移动构造了)
  5. 主线程调用future对象中的get()方法获取到子线程set_value()所设置的值。
void func(promise<int>& pr)
{
    cout << "Child Thread Working~~~" << endl;
    cout << "Child Thread: Waiting 3 seconds!" << endl;
    this_thread::sleep_for(chrono::seconds(3));
    
    pr.set_value(3);
    this_thread::sleep_for(chrono::seconds(1));
    cout << "Child Exit" << endl;
}

int main()
{
    promise<int> pr;
    thread t(func, ref(pr));
    auto f = pr.get_future();
    this_thread::sleep_for(chrono::seconds(1));
    cout << "Get Future: " << f.get() << endl;
    t.join();
    return 0;
}

注意:

 根据现象, 我们可以发现主线程在调用f.get()时阻塞了一会。此时说明子线程还没有执行到set_value(), 此时的future对象中的共享状态不是ready, 因此主线程会被阻塞。


②: 主线程 set_value–> 给子线程

  1. 在主线程中创建promise对象
  2. 将这个promise对象通过引用传递的方式传给子线程的任务函数(ref)
  3. 主线程合适的时候调用set_value()方法, 设置future对象的值以及状态(ready)
  4. 在编码子线程时,设置依future对象的值的判断条件,当future的共享状态或者值满足条件时,执行某一任务(或终止)
void func2(promise<int>& pr)
{
    int i = 0;
    auto val = pr.get_future().get();
    if(val == 1){
        cout << "Get Value: " << val << endl;
        //do something
    }
    else{
        cout << "Get Value: " << val << endl;
        //do something
    }
}

int main()
{
    promise<int> pr;
    thread t(func2, ref(pr));
    cout << "Main Thread: Waiting 3 seconds!" << endl;
    this_thread::sleep_for(chrono::seconds(3));
    pr.set_value(1);
    t.join();
}

输出:

Main Thread: Waiting 3 seconds!
Get Value: 1

4. package_task

//包含于<future>头文件
template <class T> packaged_task;     // undefined
template <class Ret, class... Args> class packaged_task<Ret(Args...)>;

//构造函数
packaged_task() noexcept;						//default (1)
template <class Fn>
  explicit packaged_task (Fn&& fn);				//initialization (2)
packaged_task (const packaged_task&) = delete;	//copy [deleted] (3)
packaged_task (packaged_task&& x) noexcept;		//move (4)

//赋值
packaged_task& operator=(packaged_task&& rhs) noexcept; //move (1)
packaged_task& operator=(const packaged_task&) = delete;//copy [deleted] (2)

 package_task包装了一个函数对象(类似于function), 我们可以把它当做函数对象来使用。package_task可以将内部包装的函数和future绑定到一起,以便于进行后续的异步调用。因此我们可以将其理解为它自带了一个函数,并且该函数和future对象绑定到了一起,我们不需要额外定义函数方法了,直接实现package_task中的函数对象即可。

 但package_task相比于promise有个缺点,它里面包装了函数,而该函数的返回值就是future对象的值。它无法像使用promise那样灵活。

4.1 常用成员函数

 package_task中最常用的就是get_future()方法了。它能够获取到package_task中的future对象。

4.2 package_task的基本使用

 将package_task作为线程的启动函数传过去,传参方式必须是引用传递 ref()

int main()
{
    packaged_task<int(int, int)> pt_Add([](int x, int y)
    {
        cout << "Running~~~~~~~~~~" << endl;
        this_thread::sleep_for(chrono::seconds(3));
        return x + y;
    });

    future<int> fi = pt_Add.get_future();

    cout << "Start Thread!" << endl;
    thread t(ref(pt_Add), 10, 20);

    cout << "before get" << endl;
    int val = fi.get();
    cout << "val: " << val << endl;
    t.join();
    return 0;
}

输出:

Start Thread!
before get
Running~~~~~~~~~~
val: 30

5. async

//构造函数
// (1)
template<class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type>
    async (Fn&& fn, Args&&... args);

// (2)
template<class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type>
    async (launch policy, Fn&& fn, Args&&... args);	//policy是启动策略

 async是一个函数,它相比于前面的promise和package_task要高级一些。async可以直接启动一个子线程,然后让这个子线程执行对应的任务函数,任务函数的返回值就会被存储到future对象当中,future对象也就是async函数的返回值。主线程只需要接收asnyc的返回值,然后调用get()方法即可获取到future对象中保存的值。

: 更高级并不代表更好用,只是它的集成度更高一些,省去了我们要自己创建线程的步骤。async仍然有package_task的缺点,它无法像promise那样自由控制future在何时赋值。

• launch policy启动策略

策略解释
std::launch::async调用async函数时会创建新的线程,让该线程执行任务函数
std::launch::deferred调用async函数时不会创建新的线程,也不会去执行该任务函数。只有调用了async返回的future的get()方法或者wait()方法时才会去执行任务。(执行该任务的是主线程)

5.1 async的基本使用

• 使用默认的启动策略 — 调用async创建子线程, 并让该线程去执行任务

int main()
{
   future<int> f = async([](int x, int y)
   {
       cout << "Child Thread: Waiting 3 seconds!" << endl;
       this_thread::sleep_for(chrono::seconds(3));
       return x + y;
   }, 10, 20);

   this_thread::sleep_for(chrono::seconds(1));
   cout << "Get Value: " << f.get() << endl;
   return 0;
}

输出:

Child Thread: Waiting 3 seconds!
Get Value: 30


• 使用deferred启动策略 — 调用async不创建子线程

int main()
{
    future<int> f = async(launch::deferred, [](int x, int y)
    { 
        cout << "Child Thread "<< this_thread::get_id() << ": Waiting 3 seconds!" << endl;
        this_thread::sleep_for(chrono::seconds(3));
        return x + y;
    }, 10, 20);

    cout << "Main Thread "<< this_thread::get_id() <<": Working!!!" << endl;
    auto val = f.get();
    cout << "Get Value: " << val << endl;
	return 0;
}

输出:

Main Thread 1: Working!!!
Child Thread 1: Waiting 3 seconds!
Get Value: 30

 我们可以发现使用deferred策略时,是不会创建新的线程的。也就是说async的任务函数依然是由主线程自己去执行的,只不过执行的时机可以控制 (在调用get()方法时会去执行),这个机制类似于回调函数,你主动去调用get()才会去回调执行async的任务

6. promise、package_task、async的对比与总结

  1. promise类的使用相对灵活,但是需要自己创建线程,并且需要自己写一个函数对象。
  2. package_task类受限于只能使用函数返回值作为future对象的值。使用它也需要自己创建线程,但不需要额外写函数对象,直接将package_task当做函数对象去使用即可。
  3. async类集合度较高,它也受限于只能使用函数返回值作为future对象的值。但是async定义时可以自动创建线程,并让线程执行async中的任务函数。async的使用最简单,但是自由度较低。

细节总结:

  1. 调用get_future()方法, 并不会让线程被阻塞。只要调用future对象的get()方法,才可能被阻塞。(future共享状态没有ready就会被阻塞, 前提是future共享状态有效)
  2. 创建线程时,给线程传参要注意使用ref()的时机。

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

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

相关文章

干货 | 读懂 Appium 日志,让测试效率翻倍!

Appium 服务器运行时会产生很多日志&#xff0c;但是很多人并不了解其中的意义&#xff0c;也无法掌握有用的信息。本文将详细解读如何读懂 Appium 日志&#xff0c;并让你的测试效率翻倍。开启服务日志第一行显示了 Appium 版本和运行地址。$ appium[Appium] Welcome to Appiu…

曾经对程序员最好的公司,倒下了

硅谷有一家公司&#xff0c;它发明了同时代最好的CPU&#xff0c;最好的操作系统&#xff0c;最好的编程语言&#xff0c;但是由于傲慢和目光短浅&#xff0c;在短短二十多年间就走到了尽头。它就是Sun Microsystems&#xff0c;硅谷最让人惋惜的公司。1Sun的出现是个巧合。80年…

运动蓝牙耳机哪个品牌好、2023年最出色运动蓝牙耳机推荐

上班通勤、娱乐和运动时间都需要一款耳机的陪伴。相信音乐是许多健身爱好者锻炼时的必备&#xff0c;在枯燥的运动中用音乐分散注意力&#xff0c;不仅可以提高运动的积极性&#xff0c;还能让身体产生一种非常奇妙的愉悦感。不过&#xff0c;想要在运动中获得更好的聆听体验&a…

【图像处理OpenCV(C++版)】——3.2 几何变换之投影变换

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义&#xff0c;适用于平时学习、工作快…

前端高频vue面试题总结

created和mounted的区别 created:在模板渲染成html前调用&#xff0c;即通常初始化某些属性值&#xff0c;然后再渲染成视图。mounted:在模板渲染成html后调用&#xff0c;通常是初始化页面完成后&#xff0c;再对html的dom节点进行一些需要的操作。 如何从真实DOM到虚拟DOM …

springboot 全局 Date参数接收 String格式 转换异常报错

报错&#xff1a; .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type java.util.Date from String "2022-12-13 11:22:11": not a v…

C语言实现c++对象和私有成员

C语言实现c对象和私有成员 C语言实现c对象 类是C中面向对象编程思想中比较重要的组成部分&#xff0c;与结构体一样类只是一个模板只有在创建对象时才会申请内存空间&#xff0c;类其实是把具有共同特性的数据或方法&#xff08;面向对象编程中&#xff0c;一般把函数称为方法…

录屏软件哪个好用?10个免费好用的「录屏软件」推荐

想知道如何录制自己的流媒体视频吗&#xff1f;有几个选项可以让您免费录制流媒体视频&#xff1a;桌面屏幕录像机、在线工具、浏览器扩展、iOS 应用程序和 Android 应用程序。 查看下表以获取有关不同直播流媒体录像机的更多信息&#xff1a;它们的主要用途和运行的操作系统。…

基于YOLOV5的火灾检测系统(含模型)+GUI界面

基于YOLOV5的火灾检测系统 本期我们带来的内容是基于YOLOV5的火灾检测系统&#xff0c;火灾检测系统还是比较有实际意义的&#xff0c;也方便大家在背景描述中展开。废话不多说&#xff0c;还是先看效果。 完整代码下载地址:基于YOLOV5的火灾检测系统&#xff08;含模型&…

MySQL高级 SQL优化【limitcountupdate优化】

目录 1&#xff1a;SQL优化 1.1&#xff1a;limit优化 1.2&#xff1a;count优化 1.2.1&#xff1a;概述 1.2.2&#xff1a;count用法 1.3&#xff1a;update优化 1&#xff1a;SQL优化 1.1&#xff1a;limit优化 在数据量比较大时&#xff0c;如果进行limit分页查询&a…

拉伯配资|战略新兴产业火了,高增长低估值股曝光

导读&#xff1a;2022年&#xff0c;A股商场值得记录的前史性大事件不断。这一年&#xff0c;A股商场上市公司数量正式打破5000家&#xff0c;战略新兴工业上市公司数量打破2500家&#xff0c;占比初次打破50%大关。这一年&#xff0c;A股商场顶住杂乱的外部环境要素&#xff0…

一文读懂:什么是CRM?企业如何通过CRM盈利?

今天和大家分享一篇干货文章&#xff0c;主要探讨什么是CRM&#xff0c;用大白话解释企业究竟如何通过CRM盈利。文章有点长&#xff0c;但看完&#xff0c;相信你会有所收获。 一、什么是CRM CRM——客户关系管理系统&#xff0c;它不仅是一个系统&#xff0c;一个技术解决方案…

云原生安全系列 4:6个 Kubernetes 安全最佳实践

引言&#xff1a; Kubernetes为我们提供了一套很好的核心软件安全原则&#xff0c;但我们仍然需要理解并实施它们。对于 Kubernetes 集群分布式部署&#xff0c;攻击向量的数量也会增加&#xff0c;了解并尽可能限制这些攻击面的最佳实践非常重要。 即使在使用托管的 Kuberne…

以系统思维推进零信任架构演进

声明 本文是学习零信任数据动态授权桔皮书. 下载地址 http://github5.com/view/55013而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 以工程化思维推进零信任架构演进 前文所述零信任数据动态授权能力&#xff0c;是围绕数据本身的库、表、字段构建安…

光点数据中台能干啥?怎么做?用在哪?_光点科技

如今&#xff0c;数据营销已经成为许多公司数字化转型的必要选择。数据中台的支持是企业中任何高层次的业务应用不可或缺的。数据中心面向的公司业务不再是单一业务线&#xff0c;而是从公司整体角度审视业务全景&#xff0c;寻找可重用的沉淀能力。 今天&#xff0c;让我们来了…

一个 Qml MenuBar 的问题

基本情况 使用 QQuick.Control 中的 MenuBar 实现主菜单栏。菜单栏包括 File、Edit、View、Help 菜单项。点击菜单项&#xff0c;会弹出对应的菜单。 ApplicationWindow {id: windowwidth: 320height: 260visible: truemenuBar: MenuBar {Menu {title: qsTr("&File&…

List集合

首先看这个框架图&#xff1a; List集合代表一个元素有序&#xff0c;可重复的集合&#xff0c;集合中每个元素都有对应的顺序索引。List接口中增加了一些根据索引操作元素的方法&#xff1a; void add(int index,E element ) 在列表的指定位置插入该元素。 boolean addAll(in…

【数据结构】LinkedList模拟实现与简单使用

文章目录模拟实现LinkedListLinkedList的简单使用LinkedList的一些方法LinkedList的遍历简单对比一下LinkedList和ArrayList模拟实现LinkedList 在上一篇的博客中&#xff0c;我们讲解了链表的基础知识&#xff0c;并且模拟实现了一个无头单向不循环链表&#xff0c;链表的基础…

12.动态内存

文章目录动态内存12.1动态内存和智能指针12.1.1shared_ptr类make_shared函数shared_ptr的拷贝和赋值shared_ptr自动销毁所管理的对象shared_ptr还自动释放相关联的内存使用了动态生存期的资源的类12.1.2直接管理内存使用new动态分配和初始化对象动态分配的const对象内存耗尽指针…

Java集合类ArrayList应用 | 如何在字符串s1中删除有在字符串s2出现的字符?

目录 一、题干 二、题解 1. 思路 ArrayList实现 2. 代码 ArrayList实现 StringBuilder实现-1 StringBuilder实现-2 三、总结 一、题干 面试的编程题&#xff1a; s1: "welcome to Zhejiang" s2: "come" 要求输出从字符串s1删除s2中存在的字符之后…