async++框架中提供了多种并行计算的工具,其中包括parallel_for、parallel_invoke、parallel_reduce。这3中工具的使用场景略有不同,下面将对它们进行比较详细的介绍。
// 这个函数是一个递归设计
// 为什么只限制了前半部分任务完成后才可以执行后半部分任务呢?
// 我理解是因为前半部分任务使用了异步方法,而后半部分和我们使用的平时的递归没有差别
// 因此只需要等待前半部分即可。
template<typename Sched, typename Partitioner, typename Func>
void internal_parallel_for(Sched& sched, Partitioner partitioner, const Func& func)
{
// Split the partition, run inline if no more splits are possible
// 将数据集分割
auto subpart = partitioner.split();
// 判断是不是到了退出递归的条件
if (subpart.begin() == subpart.end()) {
for (auto&& i: partitioner)
func(std::forward<decltype(i)>(i));
return;
}
// Run the function over each half in parallel
// 异步执行前半部分任务,这里已经开始执行任务
auto&& t = async::local_spawn(sched, [&sched, &subpart, &func] {
detail::internal_parallel_for(sched, std::move(subpart), func);
});
// 执行后办部分任务
detail::internal_parallel_for(sched, std::move(partitioner), func);
// 为什么只需等待前半部分任务结束,请看模板函数上面的注释
t.get();
}
internal_parallel_for 是一种并行化的循环执行函数,适用于在多个线程中并行执行对容器中每个元素应用相同操作的场景。每个线程处理容器的不同部分,执行相同的操作,典型的使用场景是遍历数组或集合。关键点如下:
// 这段代码的目的是实现一个将多个函数并行执行的框架。
// 它通过递归的方式将任务拆分成更小的子任务,然后在多个线程中并行执行。
// 主要逻辑通过 parallel_invoke_internal 模板结构体来实现,递归地分割任务,并使用调度器来异步执行每个任务。
template<std::size_t Start, std::size_t Count>
struct parallel_invoke_internal {
template<typename Sched, typename Tuple>
static void run(Sched& sched, const Tuple& args)
{
auto&& t = async::local_spawn(sched, [&sched, &args] {
parallel_invoke_internal<Start + Count / 2, Count - Count / 2>::run(sched, args);
});
parallel_invoke_internal<Start, Count / 2>::run(sched, args);
t.get();
}
};
// parallel_invoke_internal 特化版本(处理单个任务)
template<std::size_t Index>
struct parallel_invoke_internal<Index, 1> {
template<typename Sched, typename Tuple>
static void run(Sched&, const Tuple& args)
{
// Make sure to preserve the rvalue/lvalue-ness of the original parameter
// std::get(args) 获取元组中的第 Index 个元素,并通过 std::forward 完美转发这个元素,
// 以保证它的值类别(左值或右值)不丢失,然后执行该元素(假设它是一个可调用对象)
std::forward<typename std::tuple_element<Index, Tuple>::type>(std::get<Index>(args))();
}
};
//parallel_invoke_internal 特化版本(处理空任务)
template<std::size_t Index>
struct parallel_invoke_internal<Index, 0> {
template<typename Sched, typename Tuple>
static void run(Sched&, const Tuple&) {}
};
parallel_invoke 是一种并行执行多个独立任务的方式,它不会依赖于数据,而是允许在多个线程中并行执行多个完全独立的任务。每个任务可以是不同的函数。关键特点如下:
// Result 是最终结果的类型。
// MapFunc 是映射函数的类型,用于将输入元素映射到某个值,它是一个可以调用的对象
// ReduceFunc 是归约函数的类型,用于将多个值合并成一个结果。
// 它的核心功能是将数据划分为多个子任务,并对每个子任务执行映射(map)操作,然后将所有映射结果通过归约(reduce)操作进行合并。
template<typename Sched, typename Partitioner, typename Result, typename MapFunc, typename ReduceFunc>
Result internal_parallel_map_reduce(Sched& sched, Partitioner partitioner, Result init, const MapFunc& map, const ReduceFunc& reduce)
{
// Split the partition, run inline if no more splits are possible
auto subpart = partitioner.split();
if (subpart.begin() == subpart.end()) {
Result out = init;
for (auto&& i: partitioner)
out = reduce(std::move(out), map(std::forward<decltype(i)>(i)));
return out;
}
// Run the function over each half in parallel
// 异步的执行前半部分任务
auto&& t = async::local_spawn(sched, [&sched, &subpart, init, &map, &reduce] {
return detail::internal_parallel_map_reduce(sched, std::move(subpart), init, map, reduce);
});
Result out = detail::internal_parallel_map_reduce(sched, std::move(partitioner), init, map, reduce);
// 将最后的结果进行归约,合并成一个结果
return reduce(std::move(out), t.get());
}
internal_parallel_map_reduce 是 map-reduce 模式的并行实现,结合了数据映射和结果归约的操作。它不仅执行映射操作(类似 parallel_for),还需要对结果进行归约(合并)。关键特点: