C++ 多线程异步操作

server/2025/1/12 15:01:09/

C++ 多线程异步操作

文章目录

  • C++ 多线程异步操作
    • std::future
      • 主要功能:
      • 如何使用 `std::future`
        • 1. 使用 `std::async` 和 `std::future`
        • 2. 使用 `std::promise` 和 `std::future`
      • `std::future` 的常用方法
      • 注意事项
      • 使用模板函数与 `std::async` 结合
    • std::packaged_task
      • 主要特点
      • 工作原理
      • 使用 `std::packaged_task` 的步骤
      • 注意事项
    • 最终总结:

std::future

std::future 是 C++11 引入的一个标准库组件,它用于异步操作的结果获取和线程之间的同步。通过 std::future,你可以在一个线程中启动一个异步任务,并在另一个线程中获取任务的执行结果。

std::future 提供了 get() 方法,用于阻塞当前线程,直到异步任务完成,并返回该任务的结果。std::futurestd::promise 一同使用时,能够在多个线程间传递值,确保数据的正确性和同步。

主要功能:

  • 获取异步结果:std::future 允许你获取某个异步任务的返回值。
  • 阻塞线程:future.get() 会阻塞调用线程,直到异步任务完成并返回结果。
  • 异常传递:如果异步任务发生异常,get() 会重新抛出该异常。

如何使用 std::future

std::future 通常与以下两种方式结合使用:

  1. std::asyncstd::future
  2. std::promisestd::future
1. 使用 std::asyncstd::future

std::async 是一个函数模板,用于异步地启动一个任务,并返回一个 std::future 对象,表示该任务的结果。可以指定任务的执行策略,异步执行任务或延迟执行任务。

#include <iostream>
#include <future>
#include <chrono>int asyncTask() {std::this_thread::sleep_for(std::chrono::seconds(2));return 42;  // 假设任务计算结果为 42
}int main() {// 启动异步任务,并获得 std::future 对象std::future<int> result = std::async(std::launch::async, asyncTask);// 主线程继续执行其他任务std::cout << "Main thread is doing other work..." << std::endl;// 阻塞并等待异步任务完成并获取结果int value = result.get();  // 这里会阻塞直到异步任务完成std::cout << "Async task result: " << value << std::endl;return 0;
}

在上述例子中:

  • std::async(std::launch::async, asyncTask) 启动了一个新的线程来执行 asyncTask 函数。
  • result.get() 等待任务完成并获取返回值 42

执行策略:

  • std::launch::async:强制异步执行,启动新线程执行任务。
  • std::launch::deferred:延迟执行,直到调用 get() 时才执行任务。
2. 使用 std::promisestd::future

std::promisestd::future 成对使用,通过 std::promise 设置值,通过 std::future 获取值。这种方式适用于不同线程之间的同步和数据传递。

#include <iostream>
#include <future>
#include <thread>void asyncTask(std::promise<int>& prom) {std::this_thread::sleep_for(std::chrono::seconds(2));prom.set_value(42);  // 设置计算结果
}int main() {std::promise<int> prom;  // 创建一个 promise 对象std::future<int> result = prom.get_future();  // 从 promise 获取 futurestd::thread t(asyncTask, std::ref(prom));  // 启动异步任务std::cout << "Main thread is doing other work..." << std::endl;int value = result.get();  // 获取异步任务的结果,这里会阻塞直到结果设置std::cout << "Async task result: " << value << std::endl;t.join();  // 等待线程结束return 0;
}

在上述例子中:

  • std::promise<int> prom 用于设置异步任务的结果。
  • std::future<int> result 用于在主线程中获取异步任务的结果。
  • t.join() 等待子线程结束。

std::future 的常用方法

  • get():阻塞当前线程并等待异步任务完成,返回任务结果。如果任务抛出了异常,get() 会重新抛出该异常。
  • valid():检查 future 是否有效(即是否与有效的异步任务关联)。
  • wait():阻塞当前线程直到任务完成,但不返回结果,只等待任务完成。
  • wait_for()wait_until():分别指定等待任务完成的最大时间,支持超时机制。

注意事项

  1. 避免重复调用 get()std::future::get() 方法只能调用一次。如果你尝试第二次调用 get(),程序会抛出 std::future_error 异常。确保每个 future 对象只调用一次 get()
std::future<int> result = std::async(std::launch::async, asyncTask);
result.get();  // 第一次调用
result.get();  // 会抛出异常,std::future_error
  1. 异常传递: 如果异步任务抛出了异常,调用 get() 时会重新抛出该异常。因此,get() 应该放在 try-catch 块中,以便处理异常。
try {int result = future.get();
} catch (const std::exception& e) {std::cout << "Exception caught: " << e.what() << std::endl;
}
  1. 阻塞行为: 调用 get() 时会阻塞当前线程,直到异步任务完成。如果任务运行时间较长,主线程可能会长时间阻塞。为避免界面卡顿,可以使用 wait_forwait_until 来设置超时。
  2. std::futurestd::promise 是一对一的关系: 每个 std::future 对象对应一个 std::promise 对象,用于线程间的单向数据传递。如果不正确管理,它们可能无法正确同步或传递数据。
  3. 超时机制: std::future::wait_forstd::future::wait_until 允许指定最大等待时间,这有助于防止死锁和阻塞。
std::future<int> result = std::async(std::launch::async, asyncTask);
if (result.wait_for(std::chrono::seconds(1)) == std::future_status::timeout) {std::cout << "Task timed out" << std::endl;
} else {int value = result.get();std::cout << "Async result: " << value << std::endl;
}

使用模板函数与 std::async 结合

我们可以将线程函数修改成一个模板函数,使它可以接受任何类型的函数(例如普通函数、Lambda 表达式、函数对象等),并将其传递给 std::async 执行.

#include <iostream>
#include <future>
#include <functional>// 模板函数,接受任意类型的可调用对象
template <typename Func, typename... Args>
auto async_task(Func&& func, Args&&... args) {// 使用 std::async 启动异步任务并返回一个 futurereturn std::async(std::launch::async, std::forward<Func>(func), std::forward<Args>(args)...);
}// 示例函数1
int add(int a, int b) {return a + b;
}// 示例函数2
std::string concatenate(const std::string& str1, const std::string& str2) {return str1 + str2;
}int main() {// 使用模板函数执行异步任务// 异步执行 add 函数std::future<int> result1 = async_task(add, 3, 4);// 异步执行 concatenate 函数std::future<std::string> result2 = async_task(concatenate, "Hello, ", "World!");// 获取结果std::cout << "Result of add: " << result1.get() << std::endl;            // 输出 7std::cout << "Result of concatenate: " << result2.get() << std::endl;    // 输出 Hello, World!return 0;
}

std::packaged_task

std::packaged_task 是 C++11 引入的一个模板类,它允许将可调用对象(如普通函数、Lambda 表达式、函数对象等)封装为一个异步任务,并提供与 std::future 配合使用的功能。通过 std::packaged_task,你可以将一个函数或操作包装成一个任务,并能够获取该任务的结果。

主要特点

  • std::packaged_task 是一种特殊的包装器,可以将一个可调用对象(例如函数、Lambda 表达式等)与一个 std::future 关联。
  • std::packaged_task 可以将任务的执行与结果的获取分开,即使任务已经开始执行,也可以在以后通过 std::future 获取其结果。
  • 它的使用场景通常是将某个异步任务提交到线程池或传递给其他异步机制,然后通过 std::future 来获取计算结果。

工作原理

  1. 任务的封装:将一个可调用对象(如函数、Lambda 表达式)封装到 std::packaged_task 中。
  2. 执行任务:通过 std::thread 或其他机制执行该任务,任务执行的结果会存储在 std::future 中。
  3. 获取结果:通过 std::future::get() 获取异步任务的结果。

使用 std::packaged_task 的步骤

  1. 定义 std::packaged_task:封装一个可调用对象。
  2. 获取 std::future:通过 std::packaged_task::get_future() 获取与该任务关联的 std::future
  3. 执行任务:通过线程或其他方式执行任务。
  4. 获取结果:通过 std::future::get() 获取异步任务的结果。
#include <iostream>
#include <future>
#include <thread>// 定义一个简单的函数
int add(int a, int b) {return a + b;
}int main() {// 创建一个 packaged_task 来封装 add 函数std::packaged_task<int(int, int)> task(add);// 获取与该任务相关联的 future 对象std::future<int> result = task.get_future();// 使用 std::thread 启动任务的执行std::thread t(std::move(task), 3, 4); // 将 task 移动到线程中执行// 等待任务完成并获取结果std::cout << "Result: " << result.get() << std::endl;  // 输出 7// 等待线程完成t.join();return 0;
}
=====================
std::packaged_task<int(int, int)> task(add);:
std::packaged_task 用于包装一个接受两个 int 类型参数并返回 int 的函数 add。
task.get_future();:
调用 get_future() 获取与 task 相关联的 std::future 对象。这个 future 对象将用来获取任务的结果。
std::thread t(std::move(task), 3, 4);:
将 task 移动到新的线程 t 中执行。
std::move 是必要的,因为 std::packaged_task 是不可拷贝的,但可以被移动。
result.get();:
等待异步任务执行完成,并获取其结果。
t.join();:
在主线程中等待工作线程完成,确保在程序退出前任务执行完毕。

注意事项

  • std::packaged_task 只能被执行一次:一旦任务开始执行并完成,它不能重新启动或重复使用。如果你需要多次执行类似的任务,需要创建新的 std::packaged_task 对象。
  • 不能拷贝 std::packaged_taskstd::packaged_task 是不可拷贝的,必须通过移动语义将其传递给线程等。

最终总结:

  1. 使用**async()**函数,是多线程操作中最简单的一种方式,不需要自己创建线程对象,并且可以得到子线程函数的返回值。
  2. 使用std::promise类,在子线程中可以传出返回值也可以传出其他数据,并且可选择在什么时机将数据从子线程中传递出来,使用起来更灵活。
  3. 使用std::packaged_task类,可以将子线程的任务函数进行包装,并且可以得到子线程的返回值。

http://www.ppmy.cn/server/157784.html

相关文章

SQLite 命令

关于《SQLite 命令》的文章&#xff0c;我可以为您提供一个概要。SQLite是一个轻量级的嵌入式关系数据库管理系统&#xff0c;它以单个文件的形式存储数据&#xff0c;非常适合用于不需要传统数据库服务器的场景。SQLite3的命令行工具&#xff08;sqlite3.exe&#xff09;是一个…

使用 Flask 搭建 Mock Server

使用 Flask 搭建 Mock Server 主要是通过模拟一些 HTTP 接口来返回预定的响应&#xff0c;这样前端或其他服务可以进行开发和测试&#xff0c;而无需依赖实际的后端服务。Flask 是一个非常轻量级的 Python Web 框架&#xff0c;因此非常适合用于构建 Mock Server。 以下将详细…

React 实战详细讲解:setState 是什么、如何更新及批量处理

在 React 中&#xff0c;setState 是用来更新组件状态的方法&#xff0c;它触发重新渲染组件的过程。理解 setState 的执行机制非常重要&#xff0c;特别是在大型应用中&#xff0c;它可以帮助你更高效地进行状态管理。 下面我们从 setState 的定义、更新类型、批量更新等方面…

PHP与ThinkPHP连接数据库示例

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…

Copula算法原理和R语言股市收益率相依性可视化分析

阅读全文&#xff1a;http://tecdat.cn/?p6193 copula是将多变量分布函数与其边缘分布函数耦合的函数&#xff0c;通常称为边缘。在本视频中&#xff0c;我们通过可视化的方式直观地介绍了Copula函数&#xff0c;并通过R软件应用于金融时间序列数据来理解它&#xff08;点击文…

AsyncOperation.allowSceneActivation导致异步加载卡死

先看这段代码&#xff0c;有个诡异的问题&#xff0c;不确定是不是bug public class Test : MonoBehaviour {void Start(){StartCoroutine(LoadScene(Ego.LoadingLevel));}IEnumerator LoadScene(string sceneName){LoadingUI.UpdateProgress(0.9f);yield return new WaitForS…

Operation001-Install

操作001&#xff1a;RabbitMQ安装 一、安装 # 拉取镜像 docker pull rabbitmq:3.13-management# -d 参数&#xff1a;后台运行 Docker 容器 # --name 参数&#xff1a;设置容器名称 # -p 参数&#xff1a;映射端口号&#xff0c;格式是“宿主机端口号:容器内端口号”。5672供…

Python 教程 - 基本语句

Python 教程 - 基本语句 条件语句循环语句for 循环while 循环breakcontinue pass 语句 条件语句 在进行逻辑判断时&#xff0c;我们需要用到条件语句&#xff0c;Python 提供了 if、elif、else 来进行逻辑判断。格式如下所示&#xff1a; if 判断条件1:执行语句1... elif 判断…