项目Github主页Coke。
作为C++协程框架,Coke当然可以优雅地发起计算任务,我们以"计算一组数据的和"为例,先直观地了解一下发起计算任务的方式。
cpp
#include <iostream>
#include <vector>
#include <numeric>
#include "coke/coke.h"
long long accumulate(const long long *first, const long long *last) {
long long r = 0;
while (first != last) {
r += *first;
++first;
}
return r;
}
int main() {
constexpr int N = 100000;
std::vector<long long> numbers(N, 0);
// 将[1, N]填充到numbers当中
std::iota(numbers.begin(), numbers.end(), 1LL);
// 将计算任务分成两个子任务
std::vector<long long> result;
const long long *begin = numbers.data(), *mid, *end;
mid = begin + N / 2;
end = begin + N;
// 同时发起两个子任务,并同步等待结果
result = coke::sync_wait(
coke::go(accumulate, begin, mid),
coke::go(accumulate, mid, end)
);
// 输出计算结果
long long total = result[0] + result[1];
std::cout << "1 + ... + " << N << " = " << total << std::endl;
return 0;
}
创建计算任务就是如此简洁,只需要向coke::go
传入可调用对象和参数就可以了。但由于C++
中有模板和函数重载,当函数声明比较复杂的时候,使用起来会比较晦涩。此时若恰好在协程上下文中,可以通过coke::switch_go_thread()
切换到计算线程,然后直接调用函数就可以了。例如
cpp
coke::Task<> switch_example(std::vector<int> v) {
// std::sort是个模板函数,需指定模板参数才是完整的函数名
// 无法编译
// co_await coke::go(std::sort, v.begin(), v.end());
// 可用,但冗长
// co_await coke::go(std::sort<std::vector<int>::iterator>, v.begin(), v.end());
// 当函数有多个模板参数或多个重载时,情况会更加复杂
// 此时直接切换计算线程,与上述调用效果相同,但更加简单直观
co_await coke::switch_go_thread();
std::sort(v.begin(), v.end(0));
}
当然这种方式有一定的限制,比如只能在协程函数中使用、不能用于同时启动多个计算任务等。但当你遇到多个计算任务和其他任务互相穿插的时候,你会再回来考虑它的,因此不必惊慌,仅把它当做语法糖,按个人喜好使用即可。
可为何一定要切换到计算线程呢,在当前线程直接计算可以吗?
在上一篇文章中,我们了解了如何发起网络任务(以Http为例)。一般来说,当网络任务完成时,后续的代码会被调度到一组用于处理该任务的线程组中运行(即C++ Workflow中的handler线程),此时可以对网络任务结果进行简单的处理,发起新的异步任务或者结束当前函数。但如果需要进行复杂的计算任务,直接在handler线程中处理很可能发生阻塞,以至于其他的网络任务无法被及时调度。为了避免这种问题,复杂的计算任务一定要切换到计算任务专属的线程当中去。
下面再通过一个复合的示例来展示其用法
cpp
coke::Task<> find_link(const std::string &url) {
coke::HttpClient cli;
coke::HttpResult result;
// 发起Http请求,获取内容
result = co_await cli.request(url);
// 简单起见,此处不赘述错误处理的过程,也忽略了Chunk Encoding
std::string_view body = coke::http_body_view(result.resp);
std::cout << body << std::endl;
// 仅当需要的时候才执行切换
if (body.size() > 1024) {
co_await coke::switch_go_thread();
std::regex url_re(R"url_re(https?://[a-zA-Z0-9\./\?=_-]+)url_re");
std::cregex_iterator it(body.begin(), body.end(), url_re), end;
while (it != end) {
std::cmatch match = *it;
std::cout << match.str() << std::endl;
++it;
}
// ... 其他复杂计算任务
}
else {
std::cout << "ignore small page" << std::endl;
}
}
在项目示例中,使用一个归并排序的例子展示了异步递归计算任务的使用方法。