原文链接:并发之(底层接口thread()、promise、packaged_task)

  • 除了前一篇文章介绍的高级接口async()和(shared)future,C++标准库还提供了一个启动及处理线程的底层接口

一、thread

thread概述

  • thread可以用来启动一个线程,其参数也接受一个callable object(函数、成员函数、函数对象、lambda)
  • callable object的传参方式与async()一样,并且也有传值调用和传引用调用的方式,详情可以参阅前一篇async()的文章
  • 例如:
std::thread t(doSomething);//...t.join();  //等待线程的结束

thread与async()的区别

  • 相比于async(),thread()不提供下面的性质:

    • thread没有所谓的发射策略C++标准库永远试着将目标函数启动于一个新的线程中。如果无法做到会抛出std::system_error并带有差错码resource_unavailable_try_agin
    • 没有接口可以处理线程结果。唯一可获得的是独一无二的线程ID
    • 如果发生异常,但为被捕捉于线程之内,程序会立刻终止并调用std::terminate()。如果想要将异常传播到线程外的某个context,必须使用exception_ptr
    • ④你必须声明是否“想要等待线程结束(调用join())”或打算“将它分离,使其运行于后台而不受任何控制(调用detach())”。如果你在thread object生命周期前不这么做,或如果它发生了一次move assignment,程序会终止并调用std::terminate()
    • ⑤如果你让线程运行于后台而main()结束了,所有线程会被鲁莽而硬性地终止

二、thread演示案例

#include <iostream>
#include <thread>
#include <future>
#include <random>
#include <chrono>
#include <exception>
using namespace std;void doSomething(int num, char c);int main()
{try {//开启一个线程(不分离)std::thread t1(doSomething, 5, '.');std::cout << "- started fg thread " << t1.get_id() << std::endl;//开启5个线程(分离)for (int i = 0; i < 5; ++i) {std::thread t(doSomething, 10, 'a' + i);std::cout << "-detach started bg thread " << t.get_id() << std::endl;t.detach();}//等待输入cin.get();//等待t1线程结束std::cout << "- join fg thread " << t1.get_id() << std::endl;t1.join();}catch (const exception& e) {std::cerr << "EXCEPTION: " << e.what() << std::endl;}
}void doSomething(int num, char c)
{try {std::default_random_engine dre(42 * c);std::uniform_int_distribution<int> id(10, 1000);for (int i = 0; i < num; ++i){this_thread::sleep_for(std::chrono::milliseconds(id(dre)));std::cout.put(c).flush();}}catch (const exception& e) { //处理exception异常std::cerr << "THREAD-EXCEPTION (thread  " << this_thread::get_id() << "):" << e.what() << std::endl;}catch (...) { //捕获其他所有异常std::cerr << "THREAD-EXCEPTION (thread  " << this_thread::get_id() << ")" << std::endl;}
}
  • 我们在打印了“acd”之后按下回车,结果如下所示

三、线程的分离(thread.detach())

  • detached thread(卸载/分离后的线程)很容易造成问题——线程分离之后就不再收到主程序的控制,因此你就无法判断其是否还在运行。因此如果detached thread访问非局部资源的话,或者以reference方式使用变量/object,要确保让detached thread运行的时候资源的生命周期没有结束
  • 如果当程序退出之后,detached thread可能还在运行,如果其仍然在访问“已被销毁”或“正在析构”的global/static object,这会导致不可预期的后果
  • 因此,对于detached thread,其建议使用规则如下:
    • detached thread应该访问局部对象的拷贝
    • 如果detached thread中使用了global/static object,你应该:
      • 确保这些global/static object在“对它们进行操作”的所有detached thread都结束(或都不再访问它们)之前不被销毁。一种做法是使用condition variable(条件变量),它让detached thread用来发信号说它们已结束。离开main()或调用exit()之前你必须先妥善设置这些condition variable,然后发信号说可进行析构了
      • 以调用quick_exit()的方式结束程序。这个函数之所以存在完全是为了以“不调用global和static object析构函数”的方式结束程序
  • 由于std::cin、std::cout、std::cerr及其global stream object按标准来说是“在程序运行期间不会被销毁”,所以detached thread访问这些object应该不会导致不可预期的行为。然而,其他问题例如“interleaved character”却有可能发生
  • 记住一个经验法则:终止detached thread的唯一安全方法就是搭配“...at_thread_exit()”函数群中的某一个。这会“强制main thread等待detached thread真正结束”。或者你也可以选择忽略这一性质而相信某位评论家所言:“Detached thread应该被移到'危险性质'的篇章中,几乎没有人需要它”

四、线程ID

  • this_thread::get_id():你可以根据this_thread命名空间来获取当前线程的ID,不需要通过线程对象获取
void doSomething();int main()
{std::thread t(doSomething);
}void doSomething()
{//打印线程IDstd::cout << "thread id: " << this_thread::get_id() << std::endl;
}
  • object.get_id():根据一个线程对象,获取其线程id
void doSomething();int main()
{std::thread t(doSomething);//打印线程IDstd::cout << "t thread id: " << t.get_id() << std::endl;
}

std::thread::id数据类型

  • 线程ID的数据类型用std::thread::id表示,线程ID独一无二,其是一个类类型
int main()
{std::thread t(doSomething);//保存线程IDstd::thread::id tThreadId = t.get_id();//打印IDstd::cout << "t thread id: " << tThreadId << std::endl;
}
  • std::thread::id有个默认构造函数,会产生一个独一无二的ID用来表现“no thread”
void doSomething();int main()
{std::thread t(doSomething);std::cout << "ID of \"no thread\":" << std::thread::id() << std::endl;
}

  • 线程ID的一些特性:

    • 线程ID支持的操作只有“比较”以及调用output操作符输出至某个stream。其他的操作不支持
    • 事实上,线程ID不是在thread启动时就生成的,而是在被使用时才生成的
void doSomething();
std::thread::id masterThreadID;int main()
{std::thread master(doSomething);masterThreadID = master.get_id();
}void doSomething()
{if (this_thread::get_id() == masterThreadID){//...}
}

五、std::promise<>

  • 设计promise<>的目的:

    • 在async()中,我们可以将async()的结果(正确的返回值/或异常)保存在一个future<>中,然后使用future<>.get()去获取,但是在thread中我们如何获取线程中可能产生的数据或者是异常呢?
    • 标准库设计了一个promise<>,它是future<>的配对兄弟,二者配合使用,可以保存一个shared shate(用来保存结果或异常)
  • 演示案例
#include <iostream>
#include <thread>
#include <future>
#include <string>
#include <exception>
using namespace std;void doSomething(std::promise<std::string>& p);int main()
{try {std::promise<std::string> p;//将p以引用的方式传入doSomething中std::thread t(doSomething, std::ref(p));t.detach();//如果p有结果返回,将结果保存到f中std::future<std::string> f(p.get_future());//调用.get()获取返回的结果std::cout << "result: " << f.get() << std::endl;}catch (const exception& e) {std::cerr << "EXCEPTION: " << e.what() << std::endl;}catch (...) {std::cerr << "EXCEPTION" << std::endl;}
}void doSomething(std::promise<std::string>& p)
{try {std::cout << "read char('x' for exceptipn):";char c = std::cin.get();if (c == 'x') {throw std::runtime_error(std::string("char ") + c + " read");}std::string s = std::string("char ") + c + " processed";//将字符串保存到p中返回p.set_value(std::move(s));}catch (...) {//将异常保存到p中返回p.set_exception(std::current_exception());}
}
  • 我们以引用的方式将promise<>传入到doSomething()函数中:

    • 如果有正确的数据,那么我们调用promise<>.set_value()将结果保存到promise<>中
    • 如果有异常,我们调用promise<>.set_exception()将异常保存到promise<>中。在此演示案例中我们将定义于<exception>内的辅助函数std::current_exception()传递给该函数,std::current_exception()会把当前异常以类型std::exception_ptr生成出来,如果没有异常,那么std::current_exception()生成nullptr
  • 与future<>的交互过程:
    • 我们可以将promise<>变量绑定到future<>上
    • 一旦promise<>调用set_...相关函数设置某个值或异常,那么shared state就存在该值或异常,此时shared state状态变为ready,于是future<>对象就可以调用get()获取shared state中promise<>返回的值或异常
    • future<>.get()会阻塞,直到shared state变为ready——有值或者异常了
  • 下面两张图是输入a和x的结果:

  • 如果想要shared state在线程结束时变成ready,以确保线程的局部对象以及其他资源在“结果被处理之前”清除,那么应该调用set_value_at_thread_exit()或set_exception_at_thread_exit()。例如:
void doSomething(std::promise<std::string>& p)
{try {//..同上p.set_value_at_thread_exit(std::move(s));}catch (...) {p.set_exception_at_thread_exit(std::current_exception());}
}
  • 相关注意事项:

    • promise和future并不仅限于多线程中,在单线程中我们也可以使用promise持有一个结果值或一个异常,然后通过一个future进行处理
    • 我们不能够既存储值又存储异常。这么做会导致std::future_error并夹带差错std::future_errc::promise_already_staisfied

六、std::packaged_task<>

  • async()接口允许你处理一个任务(task)的结果,该task会自动运行于后台
  • 然而有时候处理一个task,不需要立刻启动该task

演示案例

  • 例如,thread poll(线程池)可控制何时运行以及多个后台task同时运行
  • 我们不应该像下面这样写:
double compute(int x, int y);int main()
{std::future<double> f = std::async(compute, 7, 5);double res = f.get();
}
  • 而应该使用packaged_task<>:
double compute(int x, int y);int main()
{//创建一个taskstd::packaged_task<double(int, int)> task(compute);std::future<double> f = task.get_future();//...执行其他事情//此时才执行该tasktask(7, 5);//...执行其他事情double res = f.get();
}

C++ 标准库 底层接口thread()、promise、packaged_task相关推荐

  1. C++(标准库):45---并发之(底层接口thread()、promise、packaged_task)

    除了前一篇文章介绍的高级接口async()和(shared)future,C++标准库还提供了一个启动及处理线程的底层接口 一.thread thread概述 thread可以用来启动一个线程,其参数 ...

  2. [转] Python标准库的threading.Thread类

    这个类表示在单独的控制线程中运行的活动.有两种方法可以指定这种活动,给构造函数传递回调对象,或者在子类中重写run()方法.其他方法(除了构造函数)都不应在子类中被重写.换句话说,在子类中只有__in ...

  3. python threading_【python标准库学习】thread,threading(一)多线程的介绍和使用

    在单个程序中我们经常用多线程来处理不同的工作,尤其是有的工作需要等,那么我们会新建一个线程去等然后执行某些操作,当做完事后线程退出被回收.当一个程序运行时,就会有一个进程被系统所创建,同时也会有一个线 ...

  4. python的threading库_python标准库介绍——31 threading 模块详解

    threading 模块 (可选) ``threading`` 模块为线程提供了一个高级接口, 如 [Example 3-1 #eg-3-1] 所示. 它源自 Java 的线程实现. 和低级的 ``t ...

  5. python语言的标准库有哪些,python标准库函数有哪些

    PyFlux库函数是什么? PyFlux是Python编程语言的开源时间序列库.PyFlux是Python中为处理时间序列问题而创建的开源库. 该库有一系列极好的时间序列模型,包括但不限于 ARIMA ...

  6. 2020-11-17 1)C标准库头文件 2)C ++标准库标头

    1.C标准库头文件   https://en.cppreference.com/w/c/header   C标准库的接口由以下标头集合定义. <assert.h> 有条件编译的宏,将其参数 ...

  7. 标准库IO与系统调用IO区别与联系

    标准库IO接口: 一般程序运行起来,自动默认打开 标准输入文件  fd=0(scanf),标准输出文件  fd=1(printf),  标准错误文件  fd=2 fopen   打开文件 FILE* ...

  8. 完成OSS.Http底层HttpClient重构封装 支持标准库

    OSS.Http项目对于.Net Standard标准库的支持已经迁移完毕,OSS开源系列两个最底层的类库已经具备跨运行时支持的能力.由于OSS.Http类库是几年前我参照RestSharp的思路,完 ...

  9. C99:C标准库接口的头文件集和功能定义参考

    C标准库C99头文件 <assert.h> <complex.h> <ctype.h> <errno.h> <fenv.h> <flo ...

最新文章

  1. html5游戏 虚拟主机,基于HTML5的云虚拟主机配置界面
  2. pandas使用replace函数和正则表达式移除dataframe字符串数据列中尾部指定模式字符串(Removing trailing substring in dataframe)
  3. wxPython的简单应用
  4. redis 慢日志 slowlog
  5. [翻译] Qt QFtp功能无法被Qt 5 Network系列模块替代的说明
  6. [YTU]_2354 (实现复数类中的加运算符重载【C++运算符重载】)
  7. 【机器视觉】 assign算子
  8. 关于推荐系统的一些小结
  9. Java面向对象(3.1)--方法的重载,可变个数的形参,值传递机制,递归
  10. 50 FI配置-财务会计-固定资产-与总账集成-定义集成资产购置的技术清算科目
  11. 开账户root远程桌面
  12. 图像处理---《在图片上打印文字 FreeType库》
  13. Kotlin基础知识
  14. 【转】Quartz.NET快速入门指南
  15. 深度对抗神经网络(DANN)笔记
  16. teams快捷键_每个Microsoft Teams键盘快捷键及其使用方法
  17. ms sql 创建表_使用MS查询创建表组合
  18. 苹果计算机重装系统步骤,苹果mac系统重装_苹果电脑Mac系统重装方法
  19. java xlsm_poi读取excel(xls和xlsx,xlsm)给定单元格内容
  20. 罗永浩与王自如的约战,有不少看头

热门文章

  1. 大师级设计师才会的这个CAD技巧,你会吗?
  2. 人生苦短,我用Python。
  3. Jenkins-节点服务器配置
  4. Oracle gsd服务是什么,RAC节点服务ora.rac2.gsd的offline问题解决方法
  5. 一阶低通滤波器方程_一阶低通滤波器_一阶低通滤波器公式_一阶低通滤波器原理...
  6. Python graphics库详解
  7. Android 界面置灰
  8. java 模板生成excel_java通过模板导出excel的一个实例
  9. 超级电容怎么才能把内阻做小_超级电容多长时间可以充满电
  10. 计算机丢失vcomp110.dll,msvcr110.dll丢失怎么办?