在前几篇文章中,多次用到了协程任务上的同步等待和异步等待功能,本文将对这部分内容做进一步的介绍。首先来看一个简单的例子

#include <iostream>
#include <chrono>
#include "coke/coke.h"

coke::Task<int> prepare() {
    int ms = 100;
    int ret;

    ret = co_await coke::sleep(std::chrono::milliseconds(ms));
    if (ret != coke::STATE_SUCCESS)
        co_return ret;

    // do something else

    co_return co_await coke::sleep(std::chrono::milliseconds(ms));
}

coke::Task<void> hello() {
    int ret;

    ret = co_await prepare(100);
    if (ret == coke::STATE_SUCCESS)
        std::cout << "Hello world" << std::endl;
}

int main() {
    coke::sync_wait(hello());
    return 0;
}

在这个例子中,main是我们经常会见到的C++函数,而hello则是一个协程,在C++协程中,使用co_await来实现对可等待体的异步等待。在hello协程中,先展示了如何使用co_await异步等待prepare协程并获取其返回值,然后再输出Hello world

coke::Task<T>是满足C++ 20协程约束的类型,在coke中,返回coke::Task<T>且使用了co_awaitco_return的函数是一个协程,而T则是协程运行完成后的返回值。由于本文只是介绍coke的使用方法,不会深入介绍协程的太多概念,如果此前未从了解过协程,请一定避免陷入“必须先完全搞懂底层机制再学如何使用”的泥淖,就像小学教加法交换律前不会教阿贝尔群的概念一样,先把简单的用法搞起来,以后有的是机会了解机制。

看到这里,熟悉C++ Workflow的读者可以感受到:coke中的协程与workflow中的串行(SeriesWork)很相似,都是将一组异步任务串起来逐个执行的组件;而coke::sleepWFTimerTask很相似,都是用来完成特定任务的基础组件。但隐约感觉还少一个组件:并行(ParallelWork)。令人惊喜的是,在协程中组织任务的方式非常直观和灵活,无需复杂的机制来实现并行,而是提供了方便的coke::async_wait接口,只需要将一组任务交给它即可实现并行。

#include <iostream>
#include <chrono>
#include <vector>
#include "coke/coke.h"

coke::Task<int> sleep(int ms) {
    int ret = co_await coke::sleep(std::chrono::milliseconds(ms));
    co_return ret;
}

coke::Task<> parallel(int n) {
    std::vector<coke::Task<int>> tasks;
    tasks.reserve(n);
    for (int i = 0; i < n; i++)
        tasks.emplace_back(sleep(100));

    std::vector<int> rets;
    rets = co_await coke::async_wait(std::move(tasks));
    std::cout << "Parallel done" << std::endl;
}

int main() {
    coke::sync_wait(parallel(6));
    return 0;
}

细心的读者会发现其中一个瑕疵,只有具有相同返回值的协程才能被传递给coke::async_wait,这并不是因为技术或语言上的限制,而是不希望通过一系列奇技淫巧搞出来一个语法怪兽,令人难以读懂和正确使用。对于真的有这种需求的场景,目前可以通过封装成无返回值(coke::Task<void>)的协程来实现。

对于同步等待,几乎每个示例代码都会看到coke::sync_wait,毕竟C++的函数入口目前还是同步方式,必然需要同步机制来等待协程运行完成。对于同步等待的使用方式,作者认为只需要常见的这一种即可,通过这种方式启动一个协程,其他所有的操作都以异步等待的方式实现,并牢记“仅在同步函数中使用同步等待,仅在协程中使用异步等待”。coke认为所有使用者都是聪慧的,所以不会采取任何措施来避免线程都被同步等待阻塞的情况。

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

附录:目前支持的所有同步/异步等待方法

#include <iostream>
#include <chrono>
#include <vector>
#include "coke/coke.h"

coke::Task<int> sleep(int ms) {
    int ret = co_await coke::sleep(std::chrono::milliseconds(ms));
    co_return ret;
}

int func() {
    return 1;
}

void sync_wait_example() {
    int ret;
    std::vector<int> vret;

    // 1. 等待单个coke::Task
    ret = coke::sync_wait(sleep(100));
    std::cout << "sync wait one coke::Task " << ret << std::endl;

    // 2. 等待多个coke::Task
    std::vector<coke::Task<int>> tasks;
    for (int i = 0; i < 3; i++) {
        tasks.emplace_back(sleep(100));
    }
    vret = coke::sync_wait(std::move(tasks));
    std::cout << "sync wait multi coke::Task\n";

    // 3. 等待多个coke::Task,语法糖,适用于任务数量确定的情况
    vret = coke::sync_wait(
        sleep(100), sleep(100)
    );
    std::cout << "sync wait multi coke::Task\n";

    // 4. 等待单个coke::*Awaiter
    ret = coke::sync_wait(coke::sleep(std::chrono::milliseconds(100)));
    std::cout << "sync wait one SleepAwaiter " << ret << std::endl;

    // 5. 等待多个相同类型的coke::*Awaiter
    std::vector<coke::SleepAwaiter> awaiters;
    for (int i = 0; i < 3; i++)
        awaiters.emplace_back(coke::sleep(std::chrono::milliseconds(100)));
    vret = coke::sync_wait(std::move(awaiters));
    std::cout << "sync wait multi SleepAwaiters\n";

    // 6. 等待多个返回值相同的coke::*Awaiter
    vret = coke::sync_wait(
        coke::sleep(std::chrono::milliseconds(100)),
        coke::go(func)
    );
    std::cout << "sync wait multi Awaiters\n";
}

coke::Task<> async_wait_example() {
    int ret;
    std::vector<int> vret;

    // 1. 等待单个coke::Task
    ret = co_await sleep(100);
    std::cout << "async wait one coke::Task " << ret << std::endl;

    // 2. 等待多个coke::Task
    std::vector<coke::Task<int>> tasks;
    for (int i = 0; i < 3; i++) {
        tasks.emplace_back(sleep(100));
    }
    vret = co_await coke::async_wait(std::move(tasks));
    std::cout << "async wait multi coke::Task\n";

    // 3. 等待多个coke::Task,语法糖,适用于任务数量确定的情况
    vret = co_await coke::async_wait(
        sleep(100), sleep(100)
    );
    std::cout << "async wait multi coke::Task\n";

    // 4. 等待单个coke::*Awaiter
    ret = co_await coke::sleep(std::chrono::milliseconds(100));
    std::cout << "async wait one SleepAwaiter " << ret << std::endl;

    // 5. 等待多个相同类型的coke::*Awaiter
    std::vector<coke::SleepAwaiter> awaiters;
    for (int i = 0; i < 3; i++)
        awaiters.emplace_back(coke::sleep(std::chrono::milliseconds(100)));
    vret = co_await coke::async_wait(std::move(awaiters));
    std::cout << "async wait multi SleepAwaiters\n";

    // 6. 等待多个返回值相同的coke::*Awaiter
    vret = co_await coke::async_wait(
        coke::sleep(std::chrono::milliseconds(100)),
        coke::go(func)
    );
    std::cout << "async wait multi Awaiters\n";
}

int main() {
    sync_wait_example();
    coke::sync_wait(async_wait_example());
    return 0;
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注