C++11 线程异步

2023-10-30

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()的时机。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++11 线程异步 的相关文章

  • WPF DataGrid 多选

    我读过几篇关于这个主题的文章 但很多都是来自 VS 或框架的早期版本 我想做的是从 dataGrid 中选择多行并将这些行返回到绑定的可观察集合中 我尝试创建一个属性 类型 并将其添加到可观察集合中 它适用于单个记录 但代码永远不会触发多个
  • 调用 McAfee 病毒扫描引擎

    我收到客户的请求 要求使用他们服务器上的 McAfee 病毒扫描将病毒扫描集成到应用程序中 我做了一些调查 发现 McScan32 dll 是主要的扫描引擎 它导出各种看起来有用的函数 我还发现提到了 McAfee Scan Engine
  • 在 xaml 中编写嵌套类型时出现设计时错误

    我创建了一个用户控件 它接受枚举类型并将该枚举的值分配给该用户控件中的 ComboBox 控件 很简单 我在数据模板中使用此用户控件 当出现嵌套类型时 问题就来了 我使用这个符号来指定 EnumType x Type myNamespace
  • 类型中的属性名称必须是唯一的

    我正在使用 Entity Framework 5 并且有以下实体 public class User public Int32 Id get set public String Username get set public virtual
  • 为什么 GCC 不允许我创建“内联静态 std::stringstream”?

    我将直接前往 MCVE include
  • 重载 (c)begin/(c)end

    我试图超载 c begin c end类的函数 以便能够调用 C 11 基于范围的 for 循环 它在大多数情况下都有效 但我无法理解和解决其中一个问题 for auto const point fProjectData gt getPoi
  • ASP.NET Core 3.1登录后如何获取用户信息

    我试图在登录 ASP NET Core 3 1 后获取用户信息 如姓名 电子邮件 id 等信息 这是我在登录操作中的代码 var claims new List
  • C# - 当代表执行异步任务时,我仍然需要 System.Threading 吗?

    由于我可以使用委托执行异步操作 我怀疑在我的应用程序中使用 System Threading 的机会很小 是否存在我无法避免 System Threading 的基本情况 只是我正处于学习阶段 例子 class Program public
  • 空指针与 int 等价

    Bjarne 在 C 编程语言 中写道 空指针与整数零不同 但 0 可以用作空指针的指针初始值设定项 这是否意味着 void voidPointer 0 int zero 0 int castPointer reinterpret cast
  • 为什么 isnormal() 说一个值是正常的,而实际上不是?

    include
  • 如何在 Linq to SQL 中使用distinct 和 group by

    我正在尝试将以下 sql 转换为 Linq 2 SQL select groupId count distinct userId from processroundissueinstance group by groupId 这是我的代码
  • 编译时展开 for 循环内的模板参数?

    维基百科 here http en wikipedia org wiki Template metaprogramming Compile time code optimization 给出了 for 循环的编译时展开 我想知道我们是否可以
  • 相当于Linux中的导入库

    在 Windows C 中 当您想要链接 DLL 时 您必须提供导入库 但是在 GNU 构建系统中 当您想要链接 so 文件 相当于 dll 时 您就不需要链接 为什么是这样 是否有等效的 Windows 导入库 注意 我不会谈论在 Win
  • 使用特定参数从 SQL 数据库填充组合框

    我在使用参数从 sql server 获取特定值时遇到问题 任何人都可以解释一下为什么它在 winfom 上工作但在 wpf 上不起作用以及我如何修复它 我的代码 private void UpdateItems COMBOBOX1 Ite
  • 对于某些 PDF 文件,LoadIFilter() 返回 -2147467259

    我正在尝试使用 Adob e IFilter 搜索 PDF 文件 我的代码是用 C 编写的 我使用 p invoke 来获取 IFilter 的实例 DllImport query dll SetLastError true CharSet
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • 当文件流没有新数据时如何防止fgets阻塞

    我有一个popen 执行的函数tail f sometextfile 只要文件流中有数据显然我就可以通过fgets 现在 如果没有新数据来自尾部 fgets 挂起 我试过ferror and feof 无济于事 我怎样才能确定fgets 当
  • 在OpenGL中,我可以在坐标(5, 5)处精确地绘制一个像素吗?

    我所说的 5 5 正是指第五行第五列 我发现使用屏幕坐标来绘制东西非常困难 OpenGL 中的所有坐标都是相对的 通常范围从 1 0 到 1 0 为什么阻止程序员使用屏幕坐标 窗口坐标如此严重 最简单的方法可能是通过以下方式设置投影以匹配渲
  • Mono 应用程序在非阻塞套接字发送时冻结

    我在 debian 9 上的 mono 下运行一个服务器应用程序 大约有 1000 2000 个客户端连接 并且应用程序经常冻结 CPU 使用率达到 100 我执行 kill QUIT pid 来获取线程堆栈转储 但它总是卡在这个位置
  • 如何确定 CultureInfo 实例是否支持拉丁字符

    是否可以确定是否CultureInfo http msdn microsoft com en us library system globalization cultureinfo aspx我正在使用的实例是否基于拉丁字符集 我相信你可以使

随机推荐

  • 伸缩自如的ElasticSearch——ElasticSearch-sql安装及使用

    文章目录 安装ElasticSearch sql 安装es sql site 安装ElasticSearch sql 安装地址 ES sql地址 注意安装版本要与ES版本一致 这里是6 7 1 安装完成后 解压 将解压后的文件夹放入ES的p
  • 【DirectX11学习01】用一个类封装DirectX的初始化

    DirectX的初始化步骤较为繁琐 这里将DX的初始化扔进类的初始化 构造函数 里 如果构造函数里还要执行其他逻辑 那就最好把D3D的初始化扔进一个内联函数 这样能够更好地区分逻辑 下次要使用的时候 就直接继承该类 然后在这基础上写自己的东
  • 软件测试c语言代码_软件测试理论知多少?

    有源医疗器械很多都是带有软件的 今天一起了解下软件测试理论 软件测试目的 软件测试定义 软件测试原则 软件测试分类 软件测试方法 测试基本流程 软件测试定义 软件测试 英语 software testing 描述一种用来促进鉴定软件的正确性
  • CVPR 2018值得一看的25篇论文,都在这里了

    作者丨李光睿 学校丨重庆大学本科在读 研究方向丨计算机视觉 Unsupervised Person Image Synthesis in Arbitrary Poses Image Synthesis CVPR 2018 Spotlight
  • 排序算法之奇偶排序

    排序算法之奇偶排序 奇偶排序的基本思想就是先对奇数列进行一趟排序 比较奇数列和其相邻的偶数列的元素 如果逆序则交换 再对偶数列进行一趟排序 比较偶数列和其相邻的奇数列的元素 如果逆序则交换 接着对奇数列进行排序 再对偶数列进行排序 重复进行
  • windows7linux双系统,win7下安装Linux实现双系统全教程

    经过大半天的摸索与实验终于在自己的电脑上成功的装上了Win7和Linux的双系统 现在我把详细的流程给大家分享了 希望有兴趣的可以去试试 下面为大家介绍win7下安装Linux实现双系统全攻略 材料 工具 4G的U盘 Linux系统 软碟通
  • 计算机wifi无法打开,教你win10系统WiFi热点无法打开的修复教程

    最近有windows10系统用户反馈 遇到了WiFi热点无法打开的问题 打开WIFI热点我们可将我们电脑的网络共享给手机 使我们的手机能够节省一些流量 怎么办呢 就此问题 接下来小编教你win10系统WiFi热点无法打开的修复教程 很多wi
  • C# 给自己写的软件,加注册码功能

    由于永和的项目里边用到了这个功能 因此在网上找到了 直接贴过来 复用一下 为自己写的程序加一个注册功能吧 生成的机器号是根据CPU和硬盘号来的 根据自己的需求改成是否是随机生成 代码直接粘贴到新建类覆盖原代码就能直接用了 using Sys
  • 探索IP地址的应用

    无论是互联网行业还是传统行业都会用到网络 作为企业如何维护网络安全 保障网站不被攻击 数据不被泄露等 这个时候我们就会通查询IP归属地 辅助企业解决安全问题 下面介绍一下ip归属地在各行业的具体应用 1 网安行业 应用一 提升企业网络资产检
  • cocos2dx使用CocosBuilder(编辑器)完成基础骨骼动画

    转载自 黑米GameDev街区 原文链接 http www himigame com cocosbuilder 1061 html 点击订阅 本博客最新动态 及时将最新博文通知您 关于CocosBuilder 已经成为cocos2d coc
  • 抖音壁纸表情包小程序源码,可对接流量主

    抖音壁纸表情包小程序源码 可对接流量主 抖音壁纸表情包小程序源码 可对接流量主 抖音壁纸表情包小程序源码 可对接流量主 抖音壁纸表情包小程序源码 可对接流量主 星光壁纸 我的页面
  • 【C语言取反运算符】~2是多少?~-5是多少?

    标题的答案 2 3 3 2 原理是什么 我们先来看这个程序及输出的结果 容易总结出这样一个结论 i i 1 为什么呢 一言以蔽之 运算符是对i的补码 含符号位 进行取反 2的原码是0000 0010 正数补码是其本身0000 0010 取反
  • angularJS项目开发需要下载安装和配置的环境

    1 node js npm命令 nodejs org nodejs安装及环境配置参见博客园某博客 地址 http www cnblogs com linjiqin p 3765390 html 2 cordova codova apache
  • Python入门之对象与变量

    一 对象 对象是Python中最基本的概念之一 在Python中 万物皆可对象 对象有三个基本属性 即类型 身份标识 值 二 变量与赋值 1 变量 在Python中 不需要事先声明变量名及其类型 直接赋值即可创建任意类型的对象变量 不仅变量
  • 基于QT4.8.6的软键盘

    这几天接到任务要写一个软键盘 用于在Linux上运行的软件 windows下也可以 效果 1 能够实现中英文 数字 字符的切换 2 并且实现单字中文 英文 数字 符号的输入 3 使用sqlite数据库 4 完全模仿手机QQ全键盘输入样式 源
  • 【Linux】VMware虚拟机安装Linux Mint系统

    1 安装准备 虚拟机软件 VMware Workstation Pro Mint系统镜像 linuxmint 20 3 cinnamon 64bit iso 下载网址可见 网易 欢迎访问网易开源镜像站 阿里 阿里巴巴开源镜像站 清华 清华大
  • Django 知识库:as_view()解析

    Django 有函数视图和类视图 分别是这样用的 函数视图 path function view 类视图 path ClassView as view 源码 来一步步分解 as view 是个类方法 它的第一个参数 cls 表示类本身 跟实
  • 2023 咸鱼玩法进阶课程

    第一课 闲鱼高阶玩法总体概述第二课 如何找到更有价格优势的货源第三课 十有九成的货源砍价技巧 第四课 闲鱼更新课程大总结
  • 【element-ui其他icon笑脸评分使用方法,官方文档踩坑】

    前提 使用elemen的
  • C++11 线程异步

    文章目录 1 线程异步的概念 2 future 2 1 共享状态 2 2 常用成员函数 3 promise 3 1 常用成员函数 3 2 promise的基本使用 4 package task 4 1 常用成员函数 4 2 package