C++11 异步操作 std::future类

news/2024/10/3 14:19:23 标签: c++, java, jvm

阅读导航

  • 引言
  • 一、异步的概念
  • 二、应用场景
    • 1. 异步任务处理
    • 2. 并发控制
    • 3. 结果获取
  • 三、使用示例
    • 1. 使用std::async关联异步任务
      • 💻示例代码
      • 说明
    • 2. 使用std::packaged_task和std::future配合
      • (1)定义std::packaged_task
      • (2)获取std::future对象
      • (3)启动异步任务
      • (4)等待异步任务完成并获取结果
    • 3. 使用std::promise和std::future配合
      • (1)创建std::promise对象
      • (2)获取std::future对象
      • (3)传递std::future对象
      • (4)在产生结果的线程中设置结果
      • (5)在消费结果的线程中获取结果
      • 📦示例代码

引言

C++11的推出,为C++编程语言带来了革命性的变化,其中std::future类作为异步编程的核心工具,让并发和异步任务的管理变得更加简洁和高效。本文将简要介绍std::future类的基本概念和用法,并通过示例展示其在实际编程中的应用,帮助您更好地理解和利用这一C++11的新特性。

一、异步的概念

异步编程是一种编程范式,它允许程序在等待某个长时间运行的操作(如文件读写、网络通信或复杂计算)完成时,不会阻塞或挂起执行线程,而是可以继续执行其他任务。这种非阻塞的执行方式可以显著提高程序的响应性和吞吐量。

在C++中,异步编程的概念通过C++11标准引入的一系列新特性得到了极大的支持和简化,其中std::future类扮演了关键角色。std::future是一个模板类,用于表示异步操作的结果。它提供了一种机制,允许程序在将来的某个时刻访问该结果,而无需在异步操作完成之前阻塞执行线程。

🔴官方文档

二、应用场景

1. 异步任务处理

在处理需要较长时间完成的任务,如网络请求、大规模数据处理或复杂计算时,std::future 提供了一种机制来代表这些异步任务的结果。通过将这些耗时的操作从主线程中分离出来,在后台执行,我们可以让主线程继续处理其他任务,从而实现任务的并行处理。这不仅提高了程序的响应速度,还优化了整体执行效率。

2. 并发控制

在多线程编程环境中,经常需要确保某些操作在另一些操作完成之后才能执行,以维护程序的状态一致性和正确性。std::future 允许我们在多线程之间实现同步控制。通过等待std::future对象代表的异步任务完成,我们可以确保在继续执行依赖于该任务结果的操作之前,该任务已经被成功完成。这种机制有助于简化并发控制逻辑,减少错误和竞态条件的发生。

3. 结果获取

std::future 提供了一种安全且便捷的方式来获取异步任务的结果。通过调用std::future::get()成员函数,我们可以尝试检索异步操作的结果。然而,需要注意的是,如果异步操作尚未完成,调用get()函数将会阻塞当前线程,直到异步操作完成并返回结果。这种方式确保了我们在继续处理结果之前,确实已经获得了所需的数据,从而避免了潜在的数据竞争和错误。因此,std::future提供了一种可靠的机制来同步访问异步操作的结果。

三、使用示例

1. 使用std::async关联异步任务

在C++中,std::async<future>库中的一个功能强大的工具,它允许你以异步方式启动一个任务,并且这个任务可以立即返回一个std::future对象,通过这个对象你可以在未来某个时刻获取到任务的结果。使用std::async可以很方便地实现并行计算或提高程序的响应性。

💻示例代码

假设我们有两个函数,分别用于执行一些耗时的计算:

#include <iostream>
#include <future>
#include <chrono>
#include <thread>

// 第一个耗时任务
int task1() {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return 42; // 假设的返回值
}

// 第二个耗时任务
int task2() {
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
    return 24; // 假设的返回值
}

int main() {
    // 启动两个异步任务
    auto future1 = std::async(std::launch::async, task1);
    auto future2 = std::async(std::launch::async, task2);

    // 等待并获取两个异步任务的结果
    int result1 = future1.get();
    int result2 = future2.get();

    // 输出结果
    std::cout << "Task 1 result: " << result1 << std::endl;
    std::cout << "Task 2 result: " << result2 << std::endl;

    return 0;
}

说明

  1. 启动异步任务:使用std::async时,你需要指定任务的启动策略(std::launch::asyncstd::launch::deferred或它们的组合)和要异步执行的函数。在这个例子中,我们使用了std::launch::async来确保任务在新的线程中立即开始执行。

  2. 获取任务结果std::async返回一个std::future对象,这个对象代表了异步操作的结果。你可以通过调用future.get()来等待异步操作完成并获取其结果。注意,get()会阻塞调用它的线程,直到异步操作完成。

  3. 并发执行:在这个例子中,task1task2会并发执行,因为我们在主线程中几乎同时启动了它们。它们的执行顺序和完成时间取决于操作系统的调度。

在C++中,std::packaged_taskstd::future是紧密相关的,它们通常结合使用以实现异步编程和结果传递。std::packaged_task是一个可调用的对象,它封装了一个可以异步执行的函数、lambda表达式、绑定表达式或其他可调用对象,并将该函数的执行结果存储在与std::future相关联的共享状态中。

2. 使用std::packaged_task和std::future配合

(1)定义std::packaged_task

首先,需要定义一个std::packaged_task对象,并为其提供一个返回特定类型结果的函数或可调用对象。这个函数的返回类型将与std::future的类型相关联。

#include <future>
#include <iostream>

int compute_value(int x) {
    // 假设这是一个耗时的计算
    return x * x;
}

int main() {
    std::packaged_task<int(int)> task(compute_value);
    // ...
}

(2)获取std::future对象

通过调用std::packaged_taskget_future()成员函数来获取一个std::future对象。这个future对象将用于稍后检索异步操作的结果。

    std::future<int> result = task.get_future();

(3)启动异步任务

std::packaged_task对象可以作为函数对象被调用,但通常不会直接在原线程中这样做,而是将它绑定到一个线程(例如,使用std::thread)或某个异步执行机制(如线程池)上,以异步方式执行。

    std::thread worker(std::move(task), 42); // 传递任务和一个参数
    // ...
}

注意:在将std::packaged_task传递给线程之前,必须先获取std::future对象,因为一旦std::packaged_task被移动到另一个线程,你就不能再访问原始对象来获取std::future了。

(4)等待异步任务完成并获取结果

在主线程中,你可以通过调用std::futureget()方法来等待异步任务完成并获取结果。调用get()会阻塞当前线程,直到结果可用。

    worker.join(); // 等待线程完成
    std::cout << "The result is " << result.get() << std::endl;

🚨🚨注意std::future::get()只能被调用一次,因为结果一旦被取出就无法再次访问

3. 使用std::promise和std::future配合

在C++中,std::promisestd::future是紧密相关的,它们用于在不同线程之间传递值或异常。std::promise对象允许你在一个线程中设置结果值或异常,而std::future对象则用于在另一个线程中获取这些值或异常。这种机制特别适用于异步编程,其中任务的执行和结果的使用可能发生在不同的线程中。

(1)创建std::promise对象

首先,在产生结果的线程中创建一个std::promise对象。这个对象将用于设置结果值或异常。

(2)获取std::future对象

通过调用std::promise对象的get_future()成员函数来获取一个std::future对象。这个future对象将用于在另一个线程中获取结果。

(3)传递std::future对象

std::future对象传递给需要结果的线程。这通常通过函数参数、全局变量、共享数据结构或其他线程间通信机制来完成。

(4)在产生结果的线程中设置结果

在产生结果的线程中,使用std::promise对象的set_value()成员函数来设置结果值,或者使用set_exception()来设置异常(如果需要的话)。一旦设置了值或异常,与之关联的future对象就会变为“就绪”状态。

(5)在消费结果的线程中获取结果

在消费结果的线程中,使用std::future对象的get()成员函数来获取结果。如果结果已经就绪,get()将立即返回结果值。如果结果尚未就绪,get()将阻塞当前线程,直到结果变为就绪状态。

📦示例代码

#include <iostream>
#include <future>
#include <thread>

void compute_and_set_result(std::promise<int> prom) {
    // 假设这是一个耗时的计算
    int result = 42; // 假设的计算结果
    
    // 设置结果值
    prom.set_value(result);
}

int main() {
    // 创建一个promise对象
    std::promise<int> prom;
    
    // 获取与promise关联的future对象
    std::future<int> fut = prom.get_future();
    
    // 启动一个线程来执行耗时的计算并设置结果
    std::thread worker(compute_and_set_result, std::move(prom));
    
    // 等待结果并打印
    std::cout << "The result is " << fut.get() << std::endl;
    
    // 确保线程完成
    worker.join();
    
    return 0;
}

http://www.niftyadmin.cn/n/5688511.html

相关文章

【MySQL】子查询、合并查询、表的连接

目录 一、子查询 1、单行子查询 显示SMITH同一部门的员工信息 2、多行子查询 in关键字 查询和10号部门的工作岗位相同的雇员的名字、岗位、工资、部门号&#xff0c;但是筛选出的雇员的部门不能有10号部门 all关键字 查询工资比30号部门中所有雇员工资高的雇员的姓名、…

MFC工控项目实例二十一型号选择界面删除参数按钮禁用切换

承接专栏《MFC工控项目实例二十手动测试界面模拟量输入实时显示》 对于禁止使用的删除、参数按钮&#xff0c;在选中列表控件选项时切换为能够使用。 1、在TypDlg.h文件中添加代码 #include "ShadeButtonST.h" #include "BtnST.h" class CTypDlg : publi…

【洛谷】AT_abc373_c [ABC373D] Hidden Weights 的题解

【洛谷】AT_abc373_c [ABC373D] Hidden Weights 的题解 洛谷传送门 AT传送门 题解 本地WA&#xff0c;提交AC&#xff0c;奇迹 AT&#xff0c;奇迹评测机 题目大意&#xff1a; 给定 n n n 个点&#xff0c; m m m 条有向边&#xff0c;每条有向边从 u i u_i ui​ 指向…

激光切割机适用材质有哪些

激光切割机是一种利用激光束对各种材料进行高精度、高速度切割的机器设备。其适用材质广泛&#xff0c;包括但不限于以下两大类&#xff1a; 一、金属材料 不锈钢&#xff1a;激光切割机较容易切割不锈钢薄板&#xff0c;使用高功率YAG激光切割系统&#xff0c;切割不锈钢板的…

rabbitMq------客户端模块

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言消费者模块信道管理模块管理的字段提供的接口 信道内存管理连接管理类 前言 在RabbitMQ中&#xff0c;提供服务的是信道&#xff0c;因此在客⼾端的实现中&…

数据结构--包装类简单认识泛型

目录 1 包装类 1.1 基本数据类型和对应的包装类 1.2 装箱和拆箱&#xff0c;自动装箱和自动拆箱 2 什么是泛型 3 引出泛型 3.1 语法 4 泛型类的使用 4.1 语法 4.2 示例 5 泛型的上界 5.1 语法 5.2 示例 5.3 复杂示例 8 泛型方法 8.1 定义语法 8.2 示例 总结 1 …

C语言_内存函数

内存函数是 C 标准库中的一组函数&#xff0c;用于管理和操作内存。使用时需要包含头文件<string.h>。 1. memcpy的使用和模拟实现 函数形式如下&#xff1a; void* memcpy(void* destination, const void* source, size_tnum);函数解析和注意事项&#xff1a; memcp…

二、创建drf纯净项目

1)创建项目 django-admin startproject api2&#xff09;创建app django-admin startproject api_app3)修改settings.py注释掉一些没用的配置 INSTALLED_APPS [# django.contrib.admin,# django.contrib.auth,# django.contrib.contenttypes,# django.contrib.sessions,# d…