C++并发与多线程笔记八:async、future、packaged_task、promise

这篇具有很好参考价值的文章主要介绍了C++并发与多线程笔记八:async、future、packaged_task、promise。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1 前言

本文接上文 C++并发与多线程笔记七:condition_variable、wait、notify_one/all 的内容,主要记录 async、future、packaged_task、promise 概念以及用法。

2 std::async、std::future 创建后台任务并返回值

2.1 基本用法

std::async 是个函数模板,用来启动一个异步任务,启动一个异步任务后,它返回一个 std::future 类模板对象。

上述"启动一个异步任务"的本质含义就是自动创建一个线程并开始执行对应的线程入口函数,它返回一个 std::future 类模板对象,这个对象内就含有线程入口函数的返回值(即线程执行的返回结果)。

我们可以通过调用 std::future 对象的成员函数 get() 来获取结果。即 std::future 提供了一种访问异步操作结果的机制,就是说这个结果可能没办法马上拿到,在线程执行完毕后,才能拿到。

注:std::future 对象里会保存一个值,在将来的某个时刻能够拿到。

在下面的示例代码中,std::future 对象的 get() 成员函数等待线程结束并返回结果,也就是说 get() 函数在拿不到值时是阻塞的。另外,std::future 对象还有个 wait() 成员函数,等待线程返回,但函数本身并不返回结果,类似线程对象中的 join() 函数。

#include <thread>
#include <future> /* 需要包含该头文件 */
#include <iostream>
using namespace std;

/* 线程入口函数 */
int myThread() {
  cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
  std::chrono::milliseconds duration(5000); /* 定义间隔时间为 5000 ms */
  std::this_thread::sleep_for(duration);    /* 睡眠指定的间 */
  cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
  return 5;
}

int main() {
  cout << "main() start thread ID = " << std::this_thread::get_id() << endl;

  /* 定义一个std::future类模板对象,变量类型为 int, */
  /* 并通过 std::async 启动一个异步任务 myThread */
  std::future<int> result = std::async(myThread);

  cout << "continue......!" << endl;

  /* get() 函数会阻塞在这,等待 myThread 线程执行完毕后返回  */
  cout << result.get() << endl;
  // result.wait(); /* 等待线程执行完毕,但不返回值 */

  cout << "main() end thread ID = " << std::this_thread::get_id() << endl;
  return 0;
}

输出结果:

main() start thread ID = 1860
continue......!
myThread() start thread ID = 3300
# 这里会阻塞 5000 ms
myThread() end thread ID = 3300
5
main() end thread ID = 1860

注:std::future 对象的 get() 成员函数只能调用一次,否则运行时会发生异常:std::future_error

使用类成员函数作为 std::async 启动的线程回调函数有些额外的细节,示例代码如下,具体详见注释:

#include <thread>
#include <future> /* 需要包含该头文件 */
#include <iostream>
using namespace std;

class A {
 public:
  /* 线程入口函数 */
  int myThread(int myParameter) {
    cout << myParameter << endl;
    cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds duration(5000); /* 定义间隔时间为 5000 ms */
    std::this_thread::sleep_for(duration);    /* 睡眠指定的间 */
    cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
    return 5;
  }
};

int main() {
  A objA;
  int tempParameter = 12;
  cout << "main() start thread ID = " << std::this_thread::get_id() << endl;

  /* std::async 传入类成员函数作为线程回调函数:第二个参数为类对象引用,第三个参数开始为线程回调函数的入参 */
  std::future<int> result = std::async(&A::myThread, &objA, tempParameter);

  /* std::async 传入全局函数作为线程回调函数:第二个参数开始为线程回调函数的入参 */
  // std::future<int> result = std::async(myThread, tempParameter);

  cout << "continue......!" << endl;
  cout << result.get() << endl;

  cout << "main() end thread ID = " << std::this_thread::get_id() << endl;
  return 0;
}

如果使用 std::async 启动异步任务后,不调用 std::futureget() 函数或 wait() 函数,程序在主线程退出前依旧会等待异步任务(子线程)结束,但通常不建议这样操作,可能会有风险。

2.2 扩展用法

2.2.1 std::launch::deferred

我们可以通过额外向 std::async 传递一个 std::launch 类型(枚举)参数来达到一些特殊目的。比如 std::launch::deferred 表示线程入口函数调用被延迟到 std::future 对象的 get() 或者 wait() 函数被调用时才执行。

/*......*/
std::future<int> result = std::async(std::launch::deferred, &A::myThread, &objA, tempParameter);
cout << result.get() << endl;
// result.wait()
/*......*/

输出结果:

main() start thread ID = 2572
continue......!
12
myThread() start thread ID = 2572
myThread() end thread ID = 2572
5
main() end thread ID = 2572

可以看到,异步任务(线程)中打印的线程 ID 与主线程 ID 一样,也就是说 std::async 传入 std::launch::deferred 参数后,调用 std::future 对象的 get() 或者 wait() 函数启动异步任务(线程),实际上并没有创建新的线程,只起到一个延迟执行任务的功能

注:如果 std::async 传入 std::launch::deferred 参数后,没有调用 std::future 对象的 get() 或者 wait() 函数,那么这个异步任务(线程)不会被创建,也不会执行。

2.2.2 std::launch::async

std::launch 还有另一个枚举类型 async,它是默认参数,表示调用 std::async 函数时就开始创建线程,并立即开始执行,也就是上文一开始讲的用法。

3 std::packaged_task

std::packaged_task 是个类模板,它的模板参数是各种可调用对象。通过 std::packaged_task 来把各种可调用对象包装起来,方便将来作为线程入口函数调用,并且 std::packaged_task 包装的对象中可以拿到线程的 std::future 对象,示例代码如下:

#include <thread>
#include <future> /* 需要包含该头文件 */
#include <iostream>
using namespace std;

/* 线程入口函数 */
int myThread(int myParameter) {
  cout << myParameter << endl;
  cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
  std::chrono::milliseconds duration(5000); /* 定义间隔时间为 5000 ms */
  std::this_thread::sleep_for(duration);    /* 睡眠指定的间 */
  cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
  return 5;
}

int main() {
  cout << "main() start thread ID = " << std::this_thread::get_id() << endl;

  /* 把 myThread 函数通过 std::packaged_task 包装起来 */
  /* myThread 函数的返回值和入参都是 int 类型,因此模板类型为 int(int) */
  std::packaged_task<int(int)> myThreadTask(myThread);

  /* 使用包装好的 myThreadTask 对象,第二个参数为线程回调函数的入参,创建线程并开始执行  */
  std::thread mThreadObj(std::ref(myThreadTask), 12);

  mThreadObj.join(); /* 等待线程结束 */

  /* 根据 std::packaged_task 包装的对象获取 std::future 对象 */
  std::future<int> result = myThreadTask.get_future();
  cout << result.get() << endl;

  cout << "main() end thread ID = " << std::this_thread::get_id() << endl;

  return 0;
}

输出结果:

main() start thread ID = 7512
12
myThread() start thread ID = 3916
myThread() end thread ID = 3916
5
main() end thread ID = 7512

使用 std::packaged_task 包装 lambda 表达式的示例代码如下,这个 lambda 表达式的功能与上文的 myThread() 函数一样:

  /* 把 lambda 表达式通过 std::packaged_task 包装起来 */
  std::packaged_task<int(int)> myThreadTask([](int myParameter) {
    cout << myParameter << endl;
    cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds duration(5000); /* 定义间隔时间为 5000 ms */
    std::this_thread::sleep_for(duration);    /* 睡眠指定的间 */
    cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
    return 5;
  });

std::packaged_task 包装起来的可调用对象是可以直接调用的,所以从这个角度来讲,std::packaged_task 对象也是一个可调用对象,比如:

#include <future> /* 需要包含该头文件 */
#include <iostream>
using namespace std;

int main() {
  cout << "main() start thread ID = " << std::this_thread::get_id() << endl;

  /* 把 lambda 表达式通过 std::packaged_task 包装起来 */
  std::packaged_task<int(int)> myThreadTask([](int myParameter) {
    cout << myParameter << endl;
    cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds duration(5000); /* 定义间隔时间为 5000 ms */
    std::this_thread::sleep_for(duration);    /* 睡眠指定的间 */
    cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
    return 5;
  });

  /* 直接执行,并获取函数返回值 */
  myThreadTask(105);
  std::future<int> result = myThreadTask.get_future();
  cout << result.get() << endl;

  cout << "main() end thread ID = " << std::this_thread::get_id() << endl;

  return 0;
}

输出结果:

main() start thread ID = 15236
105
myThread() start thread ID = 15236
myThread() end thread ID = 15236
5
main() end thread ID = 15236

std::packaged_task 可以实现很多灵活的操作,比如创建一个容器,里面可以放入很多 std::packaged_task 对象,需要的时候再拿出来用:

#include <vector>
#include <future> /* 需要包含该头文件 */
#include <iostream>
using namespace std;

vector<std::packaged_task<int(int)>> myTasks; /* 容器 */

int main() {
  cout << "main() start thread ID = " << std::this_thread::get_id() << endl;

  /* 把 lambda 表达式通过 std::packaged_task 包装起来 */
  std::packaged_task<int(int)> myThreadTask([](int myParameter) {
    cout << myParameter << endl;
    cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds duration(5000); /* 定义间隔时间为 5000 ms */
    std::this_thread::sleep_for(duration);    /* 睡眠指定的间 */
    cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
    return 5;
  });

  /* 将 myThreadTask 对象移动到容器中,移动完毕后容器size为1,myThreadTask为空 */
  /* 此处只能用 move,不能用 copy,因为 std::packaged_task 的拷贝构造函数被指定为删除的(delete),只能移动或者引用,无法拷贝。 */
  myTasks.push_back(std::move(myThreadTask));

  /* ...... */

  /* 将容器中的 std::packaged_task 对象取出来用 */
  std::packaged_task<int(int)> myThreadTaskBak;
  auto iter = myTasks.begin();        /* 用迭代器获取容器中的第一个元素 */
  myThreadTaskBak = std::move(*iter); /* 同样用 move 取出来 */
  myTasks.erase(iter);                /* 删除容器中的第一个元素,后续代码不可以在再使用 iter */
  myThreadTaskBak(123);               /* 调用拿出来的 std::packaged_task 对象 */

  cout << "main() end thread ID = " << std::this_thread::get_id() << endl;

  return 0;
}

输出结果:

main() start thread ID = 13080
123
myThread() start thread ID = 13080
myThread() end thread ID = 13080
main() end thread ID = 13080

4 std::promise

std::promise 也是一个类模板,我们可以在某个线程中给它赋值,然后我们可以在其他线程中把这个值取出来,示例代码如下:

#include <thread>
#include <future> /* 需要包含该头文件 */
#include <iostream>
using namespace std;

void myThread(std::promise<int>& promiseValue, int calc) {
  int result;

  calc++;
  calc *= 10;

  /* 这里可以做各种运算,假设事件花了 5000 ms */
  std::chrono::milliseconds duration(5000); /* 定义间隔时间为 5000 ms */
  std::this_thread::sleep_for(duration);    /* 睡眠指定的间 */

  /* 计算出结果后,将其保存到 std::promise 对象中 */
  result = calc;
  promiseValue.set_value(result);

  return;
}

int main() {
  cout << "main() start thread ID = " << std::this_thread::get_id() << endl;

  int calc = 128;
  /* 声明一个 std::promise 对象,保存的值类型为 int */
  std::promise<int> myPpromiseValue;

  /* 创建并执行线程函数,等待其结束 */
  std::thread myThreadObj(myThread, std::ref(myPpromiseValue), 128);
  myThreadObj.join();  /* 用 std::thread 类型的对象,必须用 join 等待线程执行完毕 */

  /* promise 和 future 绑定,用于获取线程返回值 */
  std::future<int> result = myPpromiseValue.get_future();
  cout << result.get() << endl; /* get 只能调用一次  */

  cout << "main() end thread ID = " << std::this_thread::get_id() << endl;

  return 0;
}

输出结果:

main() start thread ID = 7520
1290
main() end thread ID = 7520

用法简单来说就是:通过 std::promise 保存一个值,在将来某个时刻我们通过把一个 std::future 绑定到这个 std::promise 上来得到绑定值。

也可以在上述代码中加多一个线程,在第二个子线程中获取第一个子线程设置的 std::promise 对象值,改造后的代码如下:

#include <thread>
#include <future> /* 需要包含该头文件 */
#include <iostream>
using namespace std;

void myThread(std::promise<int>& promiseValue, int calc) {
  int result;

  calc++;
  calc *= 10;

  /* 这里可以做各种运算,假设事件花了 5000 ms */
  std::chrono::milliseconds duration(5000); /* 定义间隔时间为 5000 ms */
  std::this_thread::sleep_for(duration);    /* 睡眠指定的间 */

  /* 计算出结果后,将其保存到 std::promise 对象中 */
  result = calc;
  promiseValue.set_value(result);

  return;
}

void myThreadEx(std::future<int>& futureValue) {
  auto result = futureValue.get();
  cout << "myThreadEx result = " << result << endl;
  return;
}

int main() {
  cout << "main() start thread ID = " << std::this_thread::get_id() << endl;

  int calc = 128;
  /* 声明一个 std::promise 对象,保存的值类型为 int */
  std::promise<int> myPpromiseValue;

  /* 创建并执行线程函数,等待其结束 */
  std::thread myThreadObj(myThread, std::ref(myPpromiseValue), 128);
  myThreadObj.join();

  /* promise 和 future 绑定,用于获取线程返回值 */
  std::future<int> result = myPpromiseValue.get_future();

  /* 创建并执行第二个线程,等待其结束 */
  std::thread myThreadObjEx(myThreadEx, std::ref(result));
  myThreadObjEx.join();

  cout << "main() end thread ID = " << std::this_thread::get_id() << endl;

  return 0;
}

输出结果:

main() start thread ID = 2892
myThreadEx result = 1290
main() end thread ID = 2892

std::promise 可以在线程与线程之间传递各种类型的数据。

5 总结

C++ 并发与多线程的笔记中介绍了很多库中带的类型和函数,但学习这些东西的目的,并不是要把它们都用在自己的实际开发中,相反,如果能用最少的东西写出一个稳定、高效的多线程程序,这是更好的。代码写的优雅整洁的最终目的是给人看的,能让别人轻易看懂并理解的代码才是好代码。

我们为了成长,必须要阅读一些高手写的代码,从而快速实现自己的代码积累,等量变发生质变时,我们的技术会有一个大幅的提升。

此处学习这些内容的目的主要是方便我们未来能够读懂高手的代码,至少看别人代码,遇到 std::promisestd::future 等东西时不会一头雾水。文章来源地址https://www.toymoban.com/news/detail-412341.html

到了这里,关于C++并发与多线程笔记八:async、future、packaged_task、promise的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++11并发与多线程笔记 (1)

    指在一个时间段内有多个进程在执行 两个或者更多的任务(独立的活动)同时发生(进行):一个程序同时执行多个独立的任务; 以往计算机,单核cpu(中央处理器):某一个时刻只能执行一个任务,由操作系统调度,每秒钟进行多次所谓的“任务切换”。并发的假象( 不

    2024年02月12日
    浏览(36)
  • C++11并发与多线程笔记(3)线程传参详解,detach()大坑,成员函数做线程函数

    在使用detach时,不推荐引用传递,指针传递肯定有问题 在创建线程的同时构造临时对象的方法传递参数是可行的 如果传递int这种基本数据类型,推荐使用 值传递 ,不要用引用 如果传递 类对象 ,避免使用隐式类型转换,全部都是创建线程这一行就创建出 临时对象 ,然后在

    2024年02月12日
    浏览(32)
  • C++11并发与多线程笔记(6) unique_lock(类模板)

    unique_lock 是一个类模板。 unique_lock 比 lock_guard 灵活很多 ( 多出来很多用法 ),效率差一点,内存占用多一些。 使用: unique_lockmutex myUniLock(myMutex); std::adopt_lock:标记作用,表示这个互斥量已经被lock()(方便记忆:已经被lock()收养了,不需要再次lock() ),即 不需要在构造函

    2024年02月12日
    浏览(35)
  • C++11并发与多线程笔记(7) 单例设计模式共享数据分析、解决,call_once

    程序灵活,维护起来可能方便,用设计模式理念写出来的代码很晦涩,但是别人接管、阅读代码都会很痛苦 老外应付特别大的项目时,把项目的开发经验、模块划分经验,总结整理成设计模式 中国零几年设计模式刚开始火时,总喜欢拿一个设计模式往上套,导致一个小小的

    2024年02月12日
    浏览(33)
  • 分布式集群与多线程高并发

      后台数据的处理语言有很多,Java 是对前端采集的数据的一种比较常见的开发语言。互联网移动客户端的用户量特别大,大量的数据处理需求应运而生。可移动嵌入式设备的表现形式   很多,如 PC 端,手机移动端,智能手表,Google  眼镜等。Server2client 的互联网开发模式比

    2024年02月08日
    浏览(39)
  • Python中的并发编程:多线程与多进程的比较【第124篇—多线程与多进程的比较】

    在Python编程领域中,处理并发任务是提高程序性能的关键之一。本文将探讨Python中两种常见的并发编程方式:多线程和多进程,并比较它们的优劣之处。通过代码实例和详细的解析,我们将深入了解这两种方法的适用场景和潜在问题。 多线程是一种轻量级的并发处理方式,适

    2024年03月14日
    浏览(69)
  • 再见了Future,图解JDK21虚拟线程的结构化并发

    Java为我们提供了许多启动线程和管理线程的方法。在本文中,我们将介绍一些在Java中进行并发编程的选项。我们将介绍 结构化并发 的概念,然后讨论 Java 21 中一组预览类——它使将任务拆分为子任务、收集结果并对其进行操作变得非常容易,而且不会不小心留下任何挂起的

    2024年02月05日
    浏览(47)
  • 《C++并发编程实战》读书笔记(1):线程管控

    包含头文件 thread 后,通过构建 std::thread 对象启动线程,任何可调用类型都适用于 std::thread 。 启动线程后,需要明确是等待它结束、还是任由它独自运行: 调用成员函数 join() 会先等待线程结束,然后隶属于该线程的任何存储空间都会被清除, std::thread 对象不再关联到已结

    2024年02月10日
    浏览(30)
  • 【linux 多线程并发】多线程模型下的信号通信处理,与多进程处理的比较,属于相同进程的线程信号分发机制

    ​ 专栏内容 : 参天引擎内核架构 本专栏一起来聊聊参天引擎内核架构,以及如何实现多机的数据库节点的多读多写,与传统主备,MPP的区别,技术难点的分析,数据元数据同步,多主节点的情况下对故障容灾的支持。 手写数据库toadb 本专栏主要介绍如何从零开发,开发的

    2024年01月17日
    浏览(36)
  • 《C++并发编程实战》读书笔记(2):线程间共享数据

    在C++中,我们通过构造 std::mutex 的实例来创建互斥量,调用成员函数 lock() 对其加锁,调用 unlock() 解锁。但通常更推荐的做法是使用标准库提供的类模板 std::lock_guard ,它针对互斥量实现了RAII手法:在构造时给互斥量加锁,析构时解锁。两个类都在头文件 mutex 里声明。 假设

    2024年02月10日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包