异步线程池

目录

  • 线程异步
  • 异步添加任务函数实现
  • 测试代码

线程异步

线程异步是一种编程范式,用于执行任务或操作而不阻塞主线程或其他线程的执行。这种方法特别适用于需要同时处理多个操作或在后台执行长时间运行的任务的场景。线程异步的核心思想是将耗时的操作与主执行流程分离,使得系统能够基于处理其他任务,而无需等待耗时操作完成。

  • 异步执行:与同步操作不同,异步操作不要求调用者在任务完成之前等待结果。异步操作通常会在后台线程中执行,主线程或其他线程可以继续执行其他任务。
  • 线程:在多线程编程中,异步操作通常通过创建新的线程类实现。新线程会执行异步任务,而主线程则继续进行其他操作。
    在上一节的线程池代码中,如果任务函数有返回值,我们就可以通过线程异步的方式,将子线程也就是工作的线程的返回值,传递给主线程,主线程就可以获取到子线程的返回值,从而实现线程之间的数据传递。核心操作就是修改添加任务函数 addTask

异步添加任务函数实现

#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
#include <functional>
#include <atomic>
#include <map>
#include <future>
using namespace std; //测试环境 为方便创建std命名空间 // 线程池类
class ThreadPool 
{
public:ThreadPool(int min, int max = thread::hardware_concurrency());~ThreadPool();template<typename F, typename... Args>auto addTask(F&& f, Args&&... args) -> future<typename result_of<F(Args...)>::type>;{using returnType = typename reslut_of<F(Args...)>::type;auto task = make_shared<packaged_task<returnType()>>(bind(forward<F>(f), forward<Args>(arg)...));future<returnType> res = task->get_future();{unique_locker<mutex> lock(m_queueMutex);m_tasks.emplace([task]() { (*task)(); });}m_condition.notify_one();return res;}
}

关于上述函数的细节解释:

  • addTask 的细节解释如下:
  • 模板参数 FArg... :
    • F 是一个类型参数,代表任务函数的类型或函数对象的类型。这个函数或函数对象将被传递给addTask函数来执行。
    • Arg... 是一个可变参数模板,代表传递给任务函数的参数类型。这些参数将被传递给addTask函数来执行任务函数。
  • auto 返回类型:
    • 返回类型是 future<typename result_of<F(Args...)>::type>,表示 addTask 函数会返回一个 future 对象,用于异步获取任务的结果。result_of<F(Args...)>::type 用于推断任务函数的返回类型。
    • 通过使用 typename ,我们明确告诉编译 std::result_of<F(Args...)>::type 是一个类型,而不是其他实体(例如静态成员)
  • using returnType :
    • 使用 result_of<F(Args...)>::type 来获取任务函数 F 执行后的返回类型,并将其命名为 returnType
  • make_shared<packaged_task<returnType()>>
    • std::make_shared 是一个模板函数,用于创建并返回一个 std::shared_ptr 对象,他以一种异常安全的方式分配和构造对象。这里 std::make_shared 用于创建一个指向 std::packaged_task 的共享指针。
    • std::package_task 是一个模板类,用于包装一个可调用对象(例如函数或函数对象),并允许异步执行并获取其结果。std::packaged_task 提供了一个 std::future 对象,通过该对象可以获取任务的结果。
    • returnType() 表示 packaged_task 将封装一个返回类型为 returnType 的任务。
  • bind(forward<F>(f), forward<Args>(args)...) :
    • std::bind 是一个标准库函数模板,用于绑定参数到可调用对象上,返回一个新的可调用对象。这里 std::bind 绑定了传入的函数 f 和参数 args... ,生成一个不接受参数的新函数对象。
    • std::forward 是一个模板函数,用于完美转发参数。addTask 函数参数 (F&& f, Args&&... args) 为未定引用类型, std::forward 保留了参数的值类别(左值或右值),以便在绑定时正确传递参数,不会被不必要的拷贝或移动。
  • task->get_future() :
    • get_future 返回一个 future 对象, 这个对象用于获取异步任务的结果。
  • 任务队列:
    • unique_lock<mutex> lock(m_queueMutex) 用于加锁,确保线程安全地将任务加入到任务队列中。
    • m_tasks.emplace([task]() { (*task)(); }); 将任务添加到任务队列中,这里使用了一个 lambda 函数来调用 (*task)(), 即执行封装任务。
  • 通知条件变量:
    • m_condition.notify_one() 用于通知一个等待在条件变量上的线程,有新的任务可以执行。

测试代码

int calc(int a, int b)
{int res = a + b;this_thread::sleep_for(chorno::seconds(2));return res;
}int main()
{ThreadPool pool(2, 24);vector<future<int>> results; // 存储异步任务的结果 future<int> 表示返回值为int的future对象for(int i = 0;i < 10; ++i){results.emplace_back(pool.addtask(calc, i, i * 2));}// 等待打印结果for(auto&& res : results){cout << res.get() << endl;}
}

关于 std::future 类的 get() 方法:

  • 阻塞行为:
    • 当调用 get() 方法时,如果异步操作还没有完成,调用线程会被阻塞,直到异步操作完成并且结果准备好为止。这意味着调用 get() 会暂停线程的执行,直到可以安全地获取到结果。
  • 返回值:
    • 一旦异步操作完成,get() 方法会返回异步操作的结果。返回值的类型与 std::future 对象关联的异步操作的结果类型相同。如果异步操作抛出了异常 get() 方法会重新抛出那个异常。
  • 一次性调用:
    • get() 方法只能调用一次。调用一次后,future 对象会变得无效,如果你尝试在调用 get() 后再次调用 get(),会导致未定义行为。

因此,在上述代码中,我们使用 emplace_backfuture<int> 对象添加到 results 向量中,以便在所有任务完成后,我们可以使用 get() 方法获取每个任务的结果,并打印出来。