目录
- 线程异步
- 异步添加任务函数实现
- 测试代码
线程异步
线程异步是一种编程范式,用于执行任务或操作而不阻塞主线程或其他线程的执行。这种方法特别适用于需要同时处理多个操作或在后台执行长时间运行的任务的场景。线程异步的核心思想是将耗时的操作与主执行流程分离,使得系统能够基于处理其他任务,而无需等待耗时操作完成。
- 异步执行:与同步操作不同,异步操作不要求调用者在任务完成之前等待结果。异步操作通常会在后台线程中执行,主线程或其他线程可以继续执行其他任务。
- 线程:在多线程编程中,异步操作通常通过创建新的线程类实现。新线程会执行异步任务,而主线程则继续进行其他操作。
在上一节的线程池代码中,如果任务函数有返回值,我们就可以通过线程异步的方式,将子线程也就是工作的线程的返回值,传递给主线程,主线程就可以获取到子线程的返回值,从而实现线程之间的数据传递。核心操作就是修改添加任务函数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的细节解释如下:- 模板参数
F和Arg...: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_back 将 future<int> 对象添加到 results 向量中,以便在所有任务完成后,我们可以使用 get() 方法获取每个任务的结果,并打印出来。