C++20 协程

1. 简介

协程就是一个可以挂起执行,稍后再恢复执行的函数。

C++20 中的协程是无栈式的(stackless),协程挂起时会返回到调用者或恢复者,且恢复执行所需的数据会分开存储,而不放在栈上。

只要一个函数包含 co_awaitco_yieldco_return 关键字,则它就是协程。

一个协程关联有如下对象:

  • promise 对象:在协程内部操作此对象,协程通过它提交返回值或异常;
  • 协程句柄:在协程外部操作此对象,用于恢复协程的执行或销毁协程帧;
  • 协程状态:在堆上分配的内部状态。

2. co_await 操作符

co_await 会挂起当前协程,然后返回到协程调用者恢复者(通过协程句柄来恢复该协程)。

co_await expr;

其中,expr 需要是一个 awaiter。

co_await 包含的主要操作是:

  • 首先调用 awaiter.await_ready(),如果其返回值为 false,则挂起当前协程(填充协程状态);然后,
  • 调用 awaiter.await_suspend(handle),其中 handle 是一个协程句柄,如果此函数返回 void,则立即返回到协程的调用者或恢复者;如果 awaiter.await_suspend(handle) 抛出异常,则会捕获该异常,然后恢复协程,接着重新抛出该异常;
  • 其他函数可以通过 handle.resume() 来恢复协程,协程恢复之后会执行 awaiter.await_resume(),其返回值就是 co_await expr 的执行结果,接着继续执行协程体;
  • 当协程体再次遇到 co_await 或执行完毕时,它会返回到恢复者继续执行。

值得注意的是,可以将协程句柄传递到其他线程,然后在另一个线程中恢复协程的执行!


3. 协程执行流程

协程的大致执行流程如下:

  • 构造一个 promise 对象;
  • 调用 promise.get_return_object(),并在协程首次挂起时将调用结果返回到调用者;
  • 执行 co_await promise.initial_suspend()
  • co_await promise.initial_suspend() 恢复时,开始执行协程体;
  • 当执行到协程体中的 co_return; 或协程体尾部时,调用 promise.return_void()
  • 当执行到协程体中的 co_return expr; 时,调用 promise.return_value(expr)
  • 如果协程以一个未捕获的异常终止时,会调用 promise.unhandled_exception()
  • 最后执行 co_await promise.final_suspend()

当通过 co_return 或协程句柄来销毁协程状态时,它会执行如下动作:

  • 析构 promise 对象;
  • 析构协程参数的拷贝;
  • 释放协程状态所占内存;
  • 返回到协程调用者或恢复者。
#include <iostream>
#include <chrono>
#include <coroutine>
#include <thread>
#include <functional>

using namespace std;
using namespace std::literals;

using callback_t = std::function<void(int)>;

// 异步执行(模拟耗时的计算)
void asyncAdd(int v, callback_t cb) {
	thread t([v, cb]() {
		this_thread::sleep_for(5ms);
		int result = v + 100;
		cb(result);
	});

	t.detach();
}

// 协程的返回值类型
struct Task {
private:
	Task() {}
	
public:
	struct promise_type {
		Task get_return_object() {
			return Task();
		}

		suspend_never initial_suspend() {
			return suspend_never{};
		}

		suspend_never final_suspend() noexcept {
			return suspend_never{};
		}

		void return_void() {}

		void unhandled_exception() {
			terminate();
		}
	};
};

// co_await 操作数的类型
class AddAwaitable {
public:
	AddAwaitable(int initValue)
		: m_init(initValue), m_result(0) {}

	bool await_ready() {
		return false;
	}

	// 调用异步函数
	void await_suspend(std::coroutine_handle<> handle) {
		auto cb = [handle, this](int value) mutable {
			m_result = value;
			handle.resume();
			cout << "after resume.\n";
		};

		asyncAdd(m_init, cb);
	}

	int await_resume() {
		return m_result;
	}

private:
	int m_init;
	int m_result;
};

Task addByCoroutine(int v) {
	cout << "tid1: " << this_thread::get_id() << '\n';

	int ret = co_await AddAwaitable(v);

	cout << "tid2: " << this_thread::get_id() << '\n';

	cout << "result = " << ret << '\n';
	co_return;
}

int main() {
	Task task(addByCoroutine(200));
	this_thread::sleep_for(1s);
	return 0;
}
tid1: 2896
tid2: 1140
result = 300
after resume.

其中,

struct suspend_never {
    constexpr bool await_ready() const noexcept {
        return true;
    }

    constexpr void await_suspend(coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
};

struct suspend_always {
    constexpr bool await_ready() const noexcept {
        return false;
    }

    constexpr void await_suspend(coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
};

4. co_yield 关键字

co_yield expr

等价于,

co_await promise.yield_value(expr)

例子:数字生成器

#include <iostream>
#include <coroutine>

using namespace std;

struct Generator {
	struct promise_type;
	using handle_t = coroutine_handle<promise_type>;

	Generator(const Generator&) = delete;
	Generator& operator=(const Generator&) = delete;

	~Generator() {
		if (m_handle) {
			m_handle.destroy();
		}
	}

	struct promise_type {
		int value;

		auto get_return_object() {
			return Generator(handle_t::from_promise(*this));
		}

		auto initial_suspend() {
			return suspend_always();
		}

		auto final_suspend() noexcept {
			return suspend_always();
		}

		void return_void() {}

		auto yield_value(int v) {
			value = v;
			return suspend_always();
		}

		void unhandled_exception() {
			terminate();
		}
	};

	bool next() {
		if (m_handle) {
			m_handle.resume();
			return !m_handle.done();
		}

		return false;
	}

	int value() const {
		return m_handle.promise().value;
	}

private:
	Generator(handle_t h)
		: m_handle(h) {}

private:
	handle_t m_handle;
};

Generator f(int n) {
	int value = 1;
	
	while (value <= n) {
		co_yield value++;
	}
}


int main() {
	Generator g(f(10));

	while (g.next()) {
		cout << g.value() << ' ';
	}
	cout << '\n';
}
1 2 3 4 5 6 7 8 9 10
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我无法提供代码示例,因为作为AI语言模型,我无法编写代码。但我可以为您解释一下协程的概念和用法。 协程(Coroutine)是一种用户级的轻量级线程,也称为协作式多任务。它不同于操作系统内核级的线程,协程不会被操作系统内核调度,而是由用户代码控制。在协程中,一个线程可以有多个执行流,这些执行流在适当的时候可以相互切换,从而实现多任务并发。 协程是一种非常有用的编程技术,用于编写高效、可读性强、可维护的代码。协程通常用于异步编程,因为它可以在不阻塞主线程的情况下执行耗时的操作。 以下是一个可能的协程示例: ```python import asyncio async def coroutine_1(): print('Coroutine 1 started') await asyncio.sleep(1) print('Coroutine 1 finished') async def coroutine_2(): print('Coroutine 2 started') await asyncio.sleep(2) print('Coroutine 2 finished') async def main(): task1 = asyncio.create_task(coroutine_1()) task2 = asyncio.create_task(coroutine_2()) print('Main started') await asyncio.gather(task1, task2) print('Main finished') asyncio.run(main()) ``` 在上面的示例中,我们定义了两个协程函数 coroutine_1 和 coroutine_2,这些函数用于执行一些异步任务。然后我们定义了一个主函数 main,它创建了两个任务 task1 和 task2,这些任务会在协程函数中执行。最后,我们使用 asyncio.run() 函数来运行主函数,从而启动协程并等待它们完成。 在上面的示例中,我们使用了 asyncio 库来实现协程。asyncio 是 Python 3 中的一个标准库,它提供了一些工具和函数来编写协程代码。asyncio 库的主要组件是事件循环(Event Loop),它负责调度协程的执行。我们使用 asyncio.run() 函数来创建一个新的事件循环并运行协程。然后我们使用 asyncio.create_task() 函数来创建任务,这些任务会在协程函数中执行。最后,我们使用 asyncio.gather() 函数来等待所有任务完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值