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::future
与 std::promise
一同使用时,能够在多个线程间传递值,确保数据的正确性和同步。
主要功能:
- 获取异步结果:
std::future
允许你获取某个异步任务的返回值。 - 阻塞线程:
future.get()
会阻塞调用线程,直到异步任务完成并返回结果。 - 异常传递:如果异步任务发生异常,
get()
会重新抛出该异常。
如何使用 std::future
std::future
通常与以下两种方式结合使用:
std::async
和std::future
std::promise
和std::future
1. 使用 std::async
和 std::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::promise
和 std::future
std::promise
和 std::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()
:分别指定等待任务完成的最大时间,支持超时机制。
注意事项
- 避免重复调用
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
- 异常传递: 如果异步任务抛出了异常,调用
get()
时会重新抛出该异常。因此,get()
应该放在try-catch
块中,以便处理异常。
try {int result = future.get();
} catch (const std::exception& e) {std::cout << "Exception caught: " << e.what() << std::endl;
}
- 阻塞行为: 调用
get()
时会阻塞当前线程,直到异步任务完成。如果任务运行时间较长,主线程可能会长时间阻塞。为避免界面卡顿,可以使用wait_for
或wait_until
来设置超时。 std::future
和std::promise
是一对一的关系: 每个std::future
对象对应一个std::promise
对象,用于线程间的单向数据传递。如果不正确管理,它们可能无法正确同步或传递数据。- 超时机制:
std::future::wait_for
和std::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
来获取计算结果。
工作原理
- 任务的封装:将一个可调用对象(如函数、Lambda 表达式)封装到
std::packaged_task
中。 - 执行任务:通过
std::thread
或其他机制执行该任务,任务执行的结果会存储在std::future
中。 - 获取结果:通过
std::future::get()
获取异步任务的结果。
使用 std::packaged_task
的步骤
- 定义
std::packaged_task
:封装一个可调用对象。 - 获取
std::future
:通过std::packaged_task::get_future()
获取与该任务关联的std::future
。 - 执行任务:通过线程或其他方式执行任务。
- 获取结果:通过
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_task
:std::packaged_task
是不可拷贝的,必须通过移动语义将其传递给线程等。
最终总结:
- 使用
**async()**
函数,是多线程操作中最简单的一种方式,不需要自己创建线程对象,并且可以得到子线程函数的返回值。 - 使用
std::promise
类,在子线程中可以传出返回值也可以传出其他数据,并且可选择在什么时机将数据从子线程中传递出来,使用起来更灵活。 - 使用
std::packaged_task
类,可以将子线程的任务函数进行包装,并且可以得到子线程的返回值。