C++语法|关于C++11线程类的线程回收技术:thread线程函数传参问题、std::ref、std::future、std::promise、std::packaged_task

在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 获取其结果。

基本用法

  1. 创建一个 std::packaged_task 对象,包装一个可调用对象。
  2. 获取与 std::packaged_task 关联的 std::future 对象。
  3. 将 std::packaged_task 对象传递给一个线程以执行任务。
  4. 使用 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 可以以两种方式执行任务:

  1. std::launch::async:强制在新线程中异步执行任务。
  2. std::launch::deferred:任务在调用 future.get() 或 future.wait() 时延迟执行,不会创建新线程。
  3. 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 时在调用线程中执行。如果使用默认行为,运行时将决定最适合的执行策略。

  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值