在C++11中,虽然库中的std::thread没有直接类似于pthread_join()的功能来获取子线程的返回数据,但可以通过几种方式实现相似的功能,主要是使用std::future和std::promise,或者std::async来从子线程获取数据。
文章目录
thread线程函数传参问题
我们当然也可以利用全局变量区、堆区来回收子线程返回的数据,但是我们首先需要解决 thread线程函数传参的问题。
#include <iostream>
#include <thread>
void handler1(int b) // 此处形参b,接收t1实参a的值,正确!!!
{
std::cout << "do handler1" << std::endl;
}
void handler2(int &b) // 此处形参b,想引用实参a,语法错误!!!为什么???
{
std::cout << "do handler2" << std::endl;
}
int main()
{
int a = 10;
std::thread t1(handler1, a);
t1.join();
// std::thread t2(handler2, a); 这里编译错误!!!
// t2.join();
}
如果线程函数非得用int &b这样的左值引用来接收,我们必须这样传参:
std::thread t2(handler2, std::ref(a)); // 注意这里使用std::ref(a)即可!!!
t2.join();
这是为什么呢?
如果有兴趣,可以去看施磊老师的文章:C++ thread线程函数传参原理剖析
也就是说 handler的形参必须和传入参数的类型匹配!
handler2的类型是 void(*)(int&),a的类型是int;
也就是说实参a传递的是右值,不能被一个左值引用int &b来接收!!
所以我们只要改成 void handler(int &&data)或者void handler(const int &data)都是可以解决问题的
为什么调用std::ref(data)就可以了?
我们首先需要明确一个概念:引用包装器。
它重载了很多操作符(比如解引用和函数调用),使其可以像原始引用那样使用。因此,在许多情况下,你可以用它来替代真正的引用,尤其是在API仅支持值传递的情况下。
也就是说虽然 data 在传参时是值传递 int 类型,但是经过引用包装器后,它会被强转为类型 int &data。
具体的我也是模棱两可,建议直接去看文章:
C++ thread线程函数传参原理剖析
std::future
std::future 是 C++11 引入的标准库类模板,用于表示异步操作的结果。它提供了一种机制,使一个线程可以等待另一个线程完成某项任务,并获取该任务的结果。std::future 与 std::promise、std::async、std::packaged_task 等类模板和函数一起,构成了 C++11 的异步编程模型。
std::future 的核心概念
- 异步结果:std::future 持有一个异步操作的结果,当异步操作完成后,可以通过 std::future 获取结果。
- 同步等待:std::future 提供了
get()
方法,该方法会阻塞调用线程,直到异步操作完成并返回结果。 - 状态共享:std::future 和
std::promise
共享状态,std::promise
设置结果,std::future
获取结果。
std::future 的常用方法
- get():阻塞当前线程,直到异步操作完成,并返回结果。如果 std::promise 设置了异常,则 get() 会抛出该异常。
- wait():阻塞当前线程,直到异步操作完成。
- wait_for(Duration d):等待指定的时间段,直到异步操作完成或超时。
- wait_until(TimePoint t):等待直到指定的时间点,直到异步操作完成或超时。
std::promise 和 std::future
std::promise 是一个模板类,它允许你在一个线程中设置一个值,该值可以通过与之关联的 std::future 对象在另一个线程中获取。std::promise 提供了一种机制,使得你可以在异步任务完成时通知等待的线程,并传递结果或异常。
常用方法
- set_value(T value):设置 std::promise 对象持有的值。
- set_exception(std::exception_ptr p):设置 std::promise 对象持有的异常。
- get_future():获取与 std::promise 关联的 std::future 对象。
示例一
使用 std::promise 和 std::future 在两个线程之间传递一个整数结果:
#include <iostream>
#include <thread>
#include <future>
// 子线程函数
void func1(std::promise<int> promise) {
int result = 42; // 子线程计算的结果
promise.set_value(result); // 将结果传回主线程
}
int main() {
// 创建一个 promise 对象
std::promise<int> promise;
// 获取与 promise 关联的 future 对象
std::future<int> future = promise.get_future();
// 创建并启动子线程
std::thread t(func1, std::move(promise));
// 从 future 对象中获取子线程的返回值
int result = future.get();
// 等待子线程结束
t.join();
// 输出子线程传回的结果
std::cout << "Result from thread: " << result << std::endl;
return 0;
}
- std::promise<int> 创建一个 promise 对象,用于设置子线程的返回值。
- promise.get_future() 返回一个与 promise 关联的 future 对象,用于获取子线程的返回值。
- std::thread 创建并启动子线程,传递 promise 对象。
- future.get() 获取子线程的返回值,主线程会阻塞在这里直到子线程设置了值。
- t.join() 等待子线程结束。
示例二
std::promise 还可以用于在线程之间传递异常:
#include <iostream>
#include <thread>
#include <future>
#include <stdexcept>
// 子线程函数
void promiseTask(std::promise<int> promise) {
try {
throw std::runtime_error("An error occurred"); // 抛出异常
} catch (...) {
promise.set_exception(std::current_exception()); // 捕获并设置异常
}
}
int main() {
// 创建一个 promise 对象
std::promise<int> promise;
// 获取与 promise 关联的 future 对象
std::future<int> result = promise.get_future();
// 创建并启动子线程
std::thread t(promiseTask, std::move(promise));
try {
// 获取异步任务的结果(如果有异常,将在这里抛出)
int value = result.get();
std::cout << "Result: " << value << std::endl;
} catch (const std::exception& e) {
std::cout << "Exception: " << e.what() << std::endl;
}
// 等待子线程结束
t.join();
return 0;
}
- 子线程抛出一个异常并捕获,然后调用 promise.set_exception 将异常传递给 promise 对象。
- 主线程调用 result.get(),如果 promise 设置了异常,get() 将抛出该异常。
std::packaged_task 和 std::future
std::packaged_task 是一个模板类,用于包装可调用对象(例如函数、lambda 表达式或函数对象),并允许将其结果传递给 std::future 对象。std::packaged_task 提供了一种方式来启动一个任务,并在任务完成后通过 std::future 获取其结果。
基本用法
- 创建一个 std::packaged_task 对象,包装一个可调用对象。
- 获取与 std::packaged_task 关联的 std::future 对象。
- 将 std::packaged_task 对象传递给一个线程以执行任务。
- 使用 std::future 对象获取任务的结果。
#include <iostream>
#include <thread>
#include <future>
#include <functional>
// 子线程函数
int func1(int x) {
return x * 2; // 计算结果
}
int main() {
// 创建一个 packaged_task 对象,并将子线程函数包装进去
std::packaged_task<int(int)> task(func1);
// 获取与 task 关联的 future 对象
std::future<int> result = task.get_future();
// 创建并启动子线程,传递 packaged_task 对象和参数
std::thread t(std::move(task), 5);
// 等待任务完成并获取结果
std::cout << "Result: " << result.get() << std::endl;
// 等待子线程结束
t.join();
return 0;
}
- std::packaged_task<int(int)> task(func1) 创建一个 packaged_task 对象,包装 func1 函数,它接受一个整数参数并返回一个整数。
- task.get_future() 获取与 task 关联的 future 对象,用于获取任务的结果。
- std::thread t(std::move(task), 5) 创建并启动子线程,执行 task(即 func1)并传递参数 5。
- result.get() 获取子线程的返回值,输出结果。
lambda表达式作为子线程函数
#include <iostream>
#include <thread>
#include <future>
int main() {
// 创建一个 packaged_task 对象,并将 lambda 表达式包装进去
std::packaged_task<int(int, int)> task([](int a, int b) {
return a + b;
});
// 获取与 task 关联的 future 对象
std::future<int> result = task.get_future();
// 创建并启动子线程,传递 packaged_task 对象和参数
std::thread t(std::move(task), 3, 7);
// 等待任务完成并获取结果
std::cout << "Result: " << result.get() << std::endl;
// 等待子线程结束
t.join();
return 0;
}
- std::packaged_task<int(int, int)> task([](int a, int b) { return a + b; }) 创建一个 packaged_task 对象,包装一个 lambda 表达式,该表达式接受两个整数参数并返回它们的和。
- task.get_future() 获取与 task 关联的 future 对象。
- std::thread t(std::move(task), 3, 7) 创建并启动子线程,执行 task(即 lambda 表达式)并传递参数 3 和 7。
- result.get() 获取子线程的返回值,输出结果。
成员函数作为子线程函数
#include <iostream>
#include <thread>
#include <future>
class MyClass {
public:
int multiply(int a, int b) {
return a * b;
}
};
int main() {
MyClass myObject;
// 使用 std::bind 将成员函数包装为普通函数
auto boundFunction = std::bind(&MyClass::multiply, &myObject, std::placeholders::_1, std::placeholders::_2);
// 创建一个 packaged_task 对象,并将成员函数包装进去
std::packaged_task<int(int, int)> task(boundFunction);
// 获取与 task 关联的 future 对象
std::future<int> result = task.get_future();
// 创建并启动子线程,传递 packaged_task 对象和参数
std::thread t(std::move(task), 4, 5);
// 等待任务完成并获取结果
std::cout << "Result: " << result.get() << std::endl;
// 等待子线程结束
t.join();
return 0;
}
- std::bind 将 MyClass 的成员函数 multiply 绑定到对象 myObject,并创建一个普通函数 boundFunction。
- std::packaged_task<int(int, int)> task(boundFunction) 创建一个 packaged_task 对象,包装 boundFunction。
- task.get_future() 获取与 task 关联的 future 对象。
- std::thread t(std::move(task), 4, 5) 创建并启动子线程,执行 task(即成员函数)并传递参数 4 和 5。
- result.get() 获取子线程的返回值,输出结果。
⭐️std::async 和 std::future
std::async 提供了一个比std::promise和std::package_task更简洁的方式来启动异步任务,并返回一个 std::future 对象,用于获取任务的结果。
std::async 的行为取决于其第一个参数 std::launch 的值,该值可以是 std::launch::async、std::launch::deferred 或二者的组合。下面详细解释 std::async 的工作原理及其选项。
工作原理
std::async 可以以两种方式执行任务:
- std::launch::async:强制在新线程中异步执行任务。
- std::launch::deferred:任务在调用 future.get() 或 future.wait() 时延迟执行,不会创建新线程。
- std::launch::async | std::launch::deferred(默认行为):运行时决定是创建新线程执行任务还是延迟执行任务。
使用 std::launch::async
#include <iostream>
#include <future>
int asyncTask(int x) {
return x * 2;
}
int main() {
// 启动异步任务
std::future<int> result = std::async(std::launch::async, asyncTask, 10);
// 获取异步任务的结果
std::cout << "Result: " << result.get() << std::endl;
return 0;
}
在这个示例中,std::async(std::launch::async, asyncTask, 10) 强制在新线程中异步执行 asyncTask 函数,并返回一个 std::future<int> 对象。
使用 std::launch::deferred
#include <iostream>
#include <future>
int asyncTask(int x) {
return x * 2;
}
int main() {
// 使用 std::launch::deferred 延迟执行任务
std::future<int> result = std::async(std::launch::deferred, asyncTask, 10);
// 获取异步任务的结果(任务在此时才执行)
std::cout << "Result: " << result.get() << std::endl;
return 0;
}
std::async(std::launch::deferred, asyncTask, 10) 表示任务不会立即执行,而是在调用 future.get() 时才执行。
使用默认行为(std::launch::async | std::launch::deferred)
#include <iostream>
#include <future>
int asyncTask(int x) {
return x * 2;
}
int main() {
// 使用默认行为,由运行时决定执行策略
std::future<int> result = std::async(asyncTask, 10);
// 获取异步任务的结果
std::cout << "Result: " << result.get() << std::endl;
return 0;
}
在这个示例中,std::async(asyncTask, 10) 使用默认行为,由运行时决定是立即在新线程中执行任务还是延迟执行任务。
std::launch参数总结
- std::launch::async:强制在新线程中执行任务。
- std::launch::deferred:任务在调用 future.get() 或 future.wait() 时才执行,不创建新线程。
- 默认行为(std::launch::async | std::launch::deferred):运行时决定执行策略,可以是创建新线程执行任务,也可以是延迟执行。
因此,当使用 std::async 时,是否创建新线程取决于你传递的 std::launch 参数。如果明确指定 std::launch::async,那么 std::async 将创建一个新线程来处理任务。如果使用 std::launch::deferred,则任务将在调用 get 或 wait 时在调用线程中执行。如果使用默认行为,运行时将决定最适合的执行策略。