Coke(四):优雅地调度计算任务

项目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;
    }
}

在项目示例中,使用一个归并排序的例子展示了异步递归计算任务的使用方法。

本系列文章同步发布于个人网站知乎专栏,转载请注明来源,以便读者及时获取最新内容及勘误。

相关推荐
Lumbrologist16 小时前
【C++】零基础入门 · 第 1 节:第一个程序 Hello World 与编译运行
开发语言·c++
_李小白17 小时前
【C++学习笔记】新特性之inline变量
c++·笔记·学习
桀人17 小时前
C++——模板初阶(收录在专栏C++入门到精通)
开发语言·c++
Lumbrologist18 小时前
【C++】零基础入门 · 第 2 节:变量、基本数据类型与输入输出
java·开发语言·c++
XX風18 小时前
CMake / Make / Ninja / MSVC / GCC / Clang / MSBuild —— 完整体系化理解
c++
Peter·Pan爱编程18 小时前
10. new_delete 不是 malloc_free 的包装
c++·人工智能·算法
故事和你9120 小时前
洛谷-【动态规划1】动态规划的引入2
开发语言·数据结构·c++·算法·动态规划·图论
fpcc20 小时前
c++编程实践——历史记录的管理
c++
玖笙&21 小时前
✨WPF编程基础【3.3】:容器控件(附源码)
c++·wpf·visual studio
汉克老师1 天前
GESP5级C++考试语法知识(十七、二分算法提高篇(二))
c++·算法·二分算法·gesp5级·gesp五级·二分算法易错点