首页 > 编程语言 > 详细

C++ coroutine

时间:2019-10-29 20:32:57      阅读:101      评论:0      收藏:0      [点我收藏+]

C++20 协程

本文主要来源于

https://lewissbaker.github.io/2017/09/25/coroutine-theory

https://blog.panicsoftware.com/coroutines-introduction/

https://kirit.com/How%20C%2B%2B%20coroutines%20work/My%20first%20coroutine

https://en.cppreference.com/w/cpp/language/coroutines

 

协程

A coroutine is a generalization of a function that allows the function to be suspended and then later resumed. It generalizes the operations of a function by separating out some of the steps performed in the Call and Return operations into three extra operations: SuspendResume and Destroy.

Since coroutines can be suspended without destroying the activation frame, we can no longer guarantee that activation frame lifetimes will be strictly nested. This means that activation frames cannot in general be allocated using a stack data-structure and so may need to be stored on the heap instead.

可以说协程是一种支持Suspend与Resume的函数,也因此不能用一般函数的栈来实现。

 

stackful vs  stackless 协程区别

stackful : create a separate stack for each coroutine, which can be used to process function calls. If there are several coroutine, several independent stack will be created.

stackless: no need to allocate whole the stack, less memory consuming. Can suspend themselves from the top level

协程的实现可以有独立的栈或者堆实现两种, C++中的为后者。后者只能在顶层suspend.

技术分享图片    技术分享图片

 

1) 每个协程都有独立于函数stack的独立栈帧,调用时候切换栈指针。

2) 把协程的空间分配在heap上

 

一种协程大致实现(以下皆指stackless coroutine)

技术分享图片     技术分享图片

 

1) 一个普通函数f,ESP(寄存器中的栈位置)和EBP(寄存器中的栈基址位置)都指向当前栈对应位置

2)调用协程x,按类似一般函数一样分配栈空间。同时在堆上分配空间,并把栈中的内容拷贝。

 

3)协程调用普通函数g,分配一般函数的栈帧。之前协程栈会存储协程堆空间位置

4)g函数返回,结果值保存在堆空间

5)协程x返回函数f, 堆空间中保持了协程的状态以及RP(resuming pointer),即下次继续执行点。函数f保存了协程的指针

6) 但一函数h通过handle继续执行协程时,仍会分配栈空间,满足传参。主要数据为堆空间的值。

 

协程的大致数据结构

 空间一般对应着数据,如上述过程所述,需要保存的数据有协程的参数,内部变量,暂停点?,promise等。

 技术分享图片

其中promise是C++对应协程规范的一种数据类型,里面有多个成员函数。通过它们,用户可以自定义协程的行为,如何时暂停、返回等。

get_return_object	  // to create return object
initial_suspend	          // entering the coroutine body
return_value 		  // called when co_return called
return_void               // called before the end of coroutine body
yield_value		  // called when co_yield called
final_suspend             // called when coroutine ends
unhandled_exception       // handle exception  

 以下是一个普通函数

int function(int a, int b) {}

 由于函数与被调用函数之间的传递或交流主要是通过返回值实现的,函数与协程也不例外。函数至少需要知道协助的指针,下次才能再次执行。

因此,C++中协程的返回类型必须能够包含promise类型,协程指针等。

 

协程返回类型与协程函数

通常表现来说,一个包含co_return, co_yield, co_await api的函数是个协程。返回类型的定义也和使用了哪个api相关。

co_return: 结束协程返回调用函数

//vs2015
#include <iostream>
#include <experimental/coroutine>
using namespace std;

template<class T>
struct test {
	// inner types
	struct promise_type;
	using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias

	// functions
	test(handle_type h):handle(h) { cout << "# Created a Test object\n"; }
	test(const test & s) = delete;
	test & operator=(const test &) = delete;
	test(test && s) :handle(s.handle) { s.handle = nullptr; }
	test& operator=(test && s) { handle = s.handle; s.handle = nullptr; return *this; }
	~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); }

	T get() 
	{ 
		cout << "# Got return value\n"; 
		if (!(this->handle.done()))
		{
			handle.resume();  //resume
			return handle.promise().value;
		}
	}



	struct promise_type
	{
		promise_type() { cout << "@ promise_type created\n"; }
		~promise_type() { cout << "@ promise_type died\n"; }

		auto get_return_object() //get return object 
		{
			cout << "@ get_return_object called\n";
			return test<T>{handle_type::from_promise(*this)};// pass handle to create "return object"
		}

		auto initial_suspend() // called before run coroutine body
		{
			cout << "@ initial_suspend is called\n";
			// return std::experimental::suspend_never{}; // dont suspend it
			return std::experimental::suspend_always{};
		}

		auto return_value(T v) // called when there is co_return expression
		{
			cout << "@ return_value is called\n";
			value = v;
			return std::experimental::suspend_never{}; // dont suspend it
													   //return std::experimental::suspend_always{};
		}

		auto final_suspend() // called at the end of coroutine body
		{
			cout << "@ final_suspend is called\n";
			return std::experimental::suspend_always{};
		}

		void unhandled_exception() //exception handler
		{
			std::exit(1);
		}

		// data
		T value;
	};

	// member variables
	handle_type handle;
};
test<int> coroutine01()
{
	std::cout << "start coroutine01\n";
	co_return 1;
	co_return 2; // will never reach here
}

int main()
{
	{
		auto a = coroutine01();
		cout << "created a corutine, try to get a value\n";
		int an = a.get();
		cout << "value is " << an << endl;
		an = a.get();
		cout << "value is " << an << endl;
	}
	return 0;
}

上述定义个一个test<int>作为返回类型,并且在该类型中定义了promise_type以及其成员函数。编译时,编译器会依据这些生产能够实现协程的执行指令。

大致执行流程可以查看log,第一步是调用get_return_object (所以我们在这个函数实现中要创建返回对象),协程进入initial_suspend->协程函数体->final_suspend协程完全结束。 函数体中遇到co_return 则调用return_value。

在initial_suspend和final_suspend函数中可以通过return true or false来决定是否暂停。

 

co_yield:返回一个序列的值

#include <iostream>
#include <experimental/coroutine>
using namespace std;

struct test {
	// inner types
	struct promise_type;
	using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias

																		   // functions
	test(handle_type h) :handle(h) { cout << "# Created a Test object\n"; }
	test(const test & s) = delete;
	test & operator=(const test &) = delete;
	test(test && s) :handle(s.handle) { s.handle = nullptr; }
	test& operator=(test && s) { handle = s.handle; s.handle = nullptr; return *this; }
	~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); }

	int current_value() {
		return handle.promise().value;
	}
	
	bool move_next()
	{
		handle.resume();
		return !handle.done();
	}

	struct promise_type
	{
		promise_type() { cout << "@ promise_type created\n"; }
		~promise_type() { cout << "@ promise_type died\n"; }

		auto get_return_object() //get return object 
		{
			cout << "@ get_return_object called\n";
			return test{handle_type::from_promise(*this)};// pass handle to create "return object"
		}

		auto initial_suspend() // called before run coroutine body
		{
			cout << "@ initial_suspend is called\n";
			return std::experimental::suspend_never{}; // dont suspend it
			//return std::experimental::suspend_always{};
		}

		auto return_void() // called when just before final_suspend, conflict with return_value
		{
			cout << "@ return_void is called\n";
			return std::experimental::suspend_never{}; // dont suspend it
													   //return std::experimental::suspend_always{};
		}

		auto yield_value(int t) // called by co_yield()
		{
			std::cout << "yield_value called\n";
			value = t;
			return std::experimental::suspend_always{};
		}

		auto final_suspend() // called at the end of coroutine body
		{
			cout << "@ final_suspend is called\n";
			return std::experimental::suspend_always{};
		}

		void unhandled_exception() //exception handler
		{
			std::exit(1);
		}

		// data
		int value;
	};

	// member variables
	handle_type handle;
};
test coroutine01(int count)
{
	std::cout << "start coroutine01\n";
	for(int i =0; i<count; i++ )
	co_yield i*2;
}

int main()
{
	{
		auto a = coroutine01(4);
		cout << "created a corutine, try to get a value\n";
		do
		{
			cout << "get value " << a.current_value() << endl;
		} while (a.move_next());
	}
	return 0;
} 

这个函数是co_yield的例子,主要区别是要在promise定义yield_value函数,而且我们定义一个协程指针(通过promise来实例化)以便我们可以多次调用协程。

通过handle.done来判断协程是否结束

 

co_await expression

Expression should can be convert to awaitor

An Awaiter type is a type that implements the three special methods that are called as part of a co_await expression: await_ready, await_suspend and await_resume.

Suspend the coroutine when the target is not ready, while awaiting completion of the computation represented by the operand expression

If the promise type, P, has a member named await_transform then <expr> is first passed into a call to promise.await_transform(<expr>) to obtain the Awaitable value 

依据expression的值决定是否暂停协程返回调用函数,或者继续执行。

其中expression需要能够转化成Awaitable类型。所谓的awaitable类型是指一种包含await_ready, await_suspend and await_resume 三个函数的数据结构。

具体怎么从表达式转化成awaitable类型如下:

技术分享图片

 

 其中依据promise类型中是否有await_transform和awaitable中是否有operator co_await操作符定义, 从表达式到awaitor的过程也不一样。

awaitor(可等待对象)是awaitable类型的实例。co_await expression 等效于 co_await awaitor, 进而调用awaitor的各个成员函数来决定co_await的结果。

 

技术分享图片

 

 如上图,co_await awaitor 等效于 awaitor->await_ready() , awaitor->await_suspend() ...

其中根据await_suspend的返回类型可以是void bool or handle.

#include <iostream>
#include <experimental/coroutine>
using namespace std;

template<class T>
struct test {
	// inner types
	struct promise_type;
	using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias

																		   // functions
	test(handle_type h) :handle(h) { cout << "# Created a Test object\n"; }
	test(const test & s) = delete;
	test & operator=(const test &) = delete;
	test(test && s) :handle(s.handle) { s.handle = nullptr; }
	test& operator=(test && s) { handle = s.handle; s.handle = nullptr; return *this; }
	~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); }

	T get()
	{
		cout << "# Got return value\n";
		if (!(this->handle.done()))
		{
			handle.resume();  //resume
			return handle.promise().value;
		}
	}

	struct promise_type
	{
		promise_type() { cout << "@ promise_type created\n"; }
		~promise_type() { cout << "@ promise_type died\n"; }

		auto get_return_object() //get return object 
		{
			cout << "@ get_return_object called\n";
			return test<T>{handle_type::from_promise(*this)};// pass handle to create "return object"
		}

		auto initial_suspend() // called before run coroutine body
		{
			cout << "@ initial_suspend is called\n";
			// return std::experimental::suspend_never{}; // dont suspend it
			return std::experimental::suspend_always{};
		}

		auto yield_value(int t) // called by co_yield()
		{
			std::cout << "yield_value called\n";
			value = t;
			return std::experimental::suspend_always{};
		}

		auto final_suspend() // called at the end of coroutine body
		{
			cout << "@ final_suspend is called\n";
			return std::experimental::suspend_always{};
		}

		void unhandled_exception() //exception handler
		{
			std::exit(1);
		}

		//T await_transform() {}

		// data
		T value;
	};

	// member variables
	handle_type handle;
};

struct AwaiableObj
{
	int a;
	AwaiableObj() :a(0) {}
	bool await_ready()
	{
		cout << "@@ await_ready called\n";
		return true;
	}

	auto await_suspend(std::experimental::coroutine_handle<> awaiting_handle)
	{
		cout << "@@ await_suspend called\n";
		// return ;
		// return true;
		return false;
		// return awaiting_handle;
	}

	auto await_resume()
	{
		cout << "@@ await_resume called\n";
		return a++;
	}
};


test<int> await_routine()
{
	auto a = AwaiableObj{};
	for (int i = 0; i < 5; i++)
	{
		auto v = co_await a;
		co_yield v;
	}
}


int main()
{
	{
		auto a = await_routine();
		auto b = a.get();
		cout << "value is " << b << endl;
		b = a.get();
		cout << "value is " << b << endl;
		b = a.get();
		cout << "value is " << b << endl;
		b = a.get();
		cout << "value is " << b << endl;
		b = a.get();
		cout << "value is " << b << endl;
	}
	return 0;
}

推荐awaitable类型定义在promise 类型之内

C++ coroutine

原文:https://www.cnblogs.com/heishanlaoy/p/11760368.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!