C++协程(一):Coroutine Theory

本文翻译自c++协程库cppcoro库作者Lewis Baker的github post,本篇为第一篇,原文内容在https://lewissbaker.github.io/2017/09/25/coroutine-theory

Coroutine Theory

This is the first of a series of posts on the C++ Coroutines TS, a new language feature that is currently on track for inclusion into the C++20 language standard.

这是关于C++协程系列的第一篇文章,C++协程是一个新的语言特性,它正在被纳入到C++20语言标准中。

In this series I will cover how the underlying mechanics of C++ Coroutines work as well as show how they can be used to build useful higher-level abstractions such as those provided by the cppcoro library.

在本系列中,我将介绍C++协程的底层机制如何工作以及如何使用它们来构建有用的更高级抽象,例如cppcoro库提供的抽象。

In this post I will describe the differences between functions and coroutines and provide a bit of theory about the operations they support. The aim of this post is introduce some foundational concepts that will help frame the way you think about C++ Coroutines.

这篇文章中我将介绍函数与协程的不同,也会提供它们所支持的一些操作的基础原理。这篇文章的目的是介绍一些帮助你理解C++协程的一些基础概念。

Coroutines are Functions are Coroutines

A coroutine is a generalisation of a function that allows the function to be suspended and then later resumed.

协程就是可以被挂起然后一段时间后再次被恢复执行的函数。

I will explain what this means in a bit more detail, but before I do I want to first review how a “normal” C++ function works.

我将在后面详细解释这句话,但是现在我想先回顾一下一个“一般”C++函数是怎么工作的。

“Normal” Functions

A normal function can be thought of as having two operations: Call and Return (Note that I’m lumping “throwing an exception” here broadly under the Return operation).

一个“一般”函数可以被认为有两个操作:调用和返回(这里我把“抛出异常”也作为广义上的返回操作)。

The Call operation creates an activation frame, suspends execution of the calling function and transfers execution to the start of the function being called.

调用操作创建一个活跃帧,将主调函数挂起并且开始执行被调函数。

The Return operation passes the return-value to the caller, destroys the activation frame and then resumes execution of the caller just after the point at which it called the function.

返回操作将返回值返回给主调函数,将(被调函数的)活跃帧销毁,然后恢复主调函数的执行。

Let’s analyse these semantics a little more…

让我们对上面一段话做一些深入分析。

Activation Frames

So what is this ‘activation frame’ thing?

首先,什么是“活跃帧”?

You can think of the activation frame as the block of memory that holds the current state of a particular invocation of a function. This state includes the values of any parameters that were passed to it and the values of any local variables.

你可以将活跃帧认为是保存所调用函数的当前状态的一块儿内存。这个当前状态包括传入的参数和函数内的局部变量。

For “normal” functions, the activation frame also includes the return-address - the address of the instruction to transfer execution to upon returning from the function - and the address of the activation frame for the invocation of the calling function. You can think of these pieces of information together as describing the ‘continuation’ of the function-call. ie. they describe which invocation of which function should continue executing at which point when this function completes.

对于“一般”函数,活跃帧还包括返回地址,这个返回地址就是从这个一般函数返回时将执行转移到的指令的地址,也就是调用(此一般)函数的主调函数活跃帧的地址(译者注:这里作者想表达,A调用B,那么B的活跃帧包含B返回时继续执行A函数语句的地址)。你可以把这些信息视作继续执行原来调用这个一般函数的主调函数的所需要的信息。这些信息包括了当函数执行完成后应当继续执行从哪个函数的哪个断点执行。

With “normal” functions, all activation frames have strictly nested lifetimes. This strict nesting allows use of a highly efficient memory allocation data-structure for allocating and freeing the activation frames for each of the function calls. This data-structure is commonly referred to as “the stack”.

对于“一般”函数,所有活跃帧都有严格的嵌套的生命周期。这种严格的嵌套使得函数调用期间所发生的内存分配和释放可以用一个高效率的数据结构完成。这种数据结构就是“栈”。

When an activation frame is allocated on this stack data structure it is often called a “stack frame”.

当一个活跃帧被分配到栈上时,我们一般称其为“栈帧”。

This stack data-structure is so common that most (all?) CPU architectures have a dedicated register for holding a pointer to the top of the stack (eg. in X64 it is the rsp register).

栈这种数据结构非常常见,几乎每一种CPU架构都会留一个寄存器专门用于存储栈顶(比如在x64架构的处理器中就是rsp寄存器)。

To allocate space for a new activation frame, you just increment this register by the frame-size. To free space for an activation frame, you just decrement this register by the frame-size.

要为新的激活帧分配空间,只需按帧大小增加这个寄存器。要为激活帧释放空间,只需按帧大小递减该寄存器。

The ‘Call’ Operation

When a function calls another function, the caller must first prepare itself for suspension.

当一个函数调用另一个函数时,主调函数必须首先准备将自己挂起。

This ‘suspend’ step typically involves saving to memory any values that are currently held in CPU registers so that those values can later be restored if required when the function resumes execution. Depending on the calling convention of the function, the caller and callee may coordinate on who saves these register values, but you can still think of them as being performed as part of the Call operation.

“挂起”操作包括将当前CPU寄存器中的所有值保存到内存中,这样当程序恢复执行主调函数时可以将这些数据从内存中加载回来。根据函数的调用约定,主调函数和被调函数可能会协商到底谁负责保存这些寄存器值,但是你仍可以认为这些保存操作是由调用操作完成的。

The caller also stores the values of any parameters passed to the called function into the new activation frame where they can be accessed by the function.

主调函数也会将传递给被调函数的参数保存到新的活跃帧中,这样被调函数就可以访问这些参数。

Finally, the caller writes the address of the resumption-point of the caller to the new activation frame and transfers execution to the start of the called function.

最后,主调函数将恢复执行主调函数的地址写入到新的活跃帧,然后将执行权转移给被调函数的开始。

In the X86/X64 architecture this final operation has its own instruction, the call instruction, that writes the address of the next instruction onto the stack, increments the stack register by the size of the address and then jumps to the address specified in the instruction’s operand.

在x86/x64架构CPU中,这最后的一步操作也有其对应的指令,即call指令,它会将(主调函数的)下一条指令的地址压到栈中,按该地址大小增加栈寄存器,然后跳转到指令操作数中指定的地址。

The ‘Return’ Operation

When a function returns via a return-statement, the function first stores the return value (if any) where the caller can access it. This could either be in the caller’s activation frame or the function’s activation frame (the distinction can get a bit blurry for parameters and return values that cross the boundary between two activation frames).

当一个函数使用return语句返回时,它将会首先将返回值(如果有的话)存到主调函数可以访问到的地方。这个地方可能是主调函数的活跃帧,也可能是被调函数的活跃帧(对于两个函数活跃帧参数和返回值交界的情况,这个区别可能会有些模糊)。

Then the function destroys the activation frame by:

  • Destroying any local variables in-scope at the return-point.
  • Destroying any parameter objects
  • Freeing memory used by the activation-frame

然后函数将会通过以下方式销毁活跃帧:

  • 在返回点销毁作用域中的所有局部变量
  • 销毁所有参数
  • 回收活跃帧所占用的内存

And finally, it resumes execution of the caller by:

  • Restoring the activation frame of the caller by setting the stack register to point to the activation frame of the caller and restoring any registers that might have been clobbered by the function.
  • Jumping to the resume-point of the caller that was stored during the ‘Call’ operation.

最后,它通过以下方式恢复主调函数的执行:

  • 通过将栈寄存器设置为指向主调函数的活跃帧,并还原任何可能已被函数占用过的寄存器,来还原主调函数的激活帧
  • 跳转到主调函数在执行“Call”操作期间存储的恢复断点

Note that as with the ‘Call’ operation, some calling conventions may split the repsonsibilities of the ‘Return’ operation across both the caller and callee function’s instructions.

注意,有些调用约定的可能会将“Return”操作的责任分配给主调函数和被调函数两者来承担

Coroutines

Coroutines generalise the operations of a function by separating out some of the steps performed in the Call and Return operations into three extra operations: Suspend, Resume and Destroy.

协程将函数中的调用返回两个操作进行分割成为了三个操作:挂起恢复销毁

The Suspend operation suspends execution of the coroutine at the current point within the function and transfers execution back to the caller or resumer without destroying the activation frame. Any objects in-scope at the point of suspension remain alive after the coroutine execution is suspended.

Suspend操作在函数中的当前点暂停协程的执行,并转去执行主调函数或调用resume的函数,但它并不销毁当前函数的活跃帧。在协程执行被挂起的时候,所有当前挂起断点作用域内的对象仍然存在。

Note that, like the Return operation of a function, a coroutine can only be suspended from within the coroutine itself at well-defined suspend-points.

注意,就像函数的Return操作一样,协程只能在定义良好的挂起点被本身挂起。

The Resume operation resumes execution of a suspended coroutine at the point at which it was suspended. This reactivates the coroutine’s activation frame.

Resume操作在挂起的协程的挂起点恢复对该协程的执行。这将重新激活协程的活跃帧。

The Destroy operation destroys the activation frame without resuming execution of the coroutine. Any objects that were in-scope at the suspend point will be destroyed. Memory used to store the activation frame is freed.

Destroy操作不恢复协程的执行直接将其活跃帧销毁,所有挂起点作用域内的对象都将会被销毁,存储活跃帧的内存也将被释放。

Coroutine activation frames

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.

由于协程可以在不被销毁活跃帧的情况下挂起,那么我们也无法保证活跃帧的生存周期将会是严格嵌套的。这意味着活跃帧无法再用栈这种数据结构存储而需要被保存在堆上。

There are some provisions in the C++ Coroutines TS to allow the memory for the coroutine frame to be allocated from the activation frame of the caller if the compiler can prove that the lifetime of the coroutine is indeed strictly nested within the lifetime of the caller. This can avoid heap allocations in many cases provided you have a sufficiently smart compiler.

在C++ Coroutines TS中有一些规定,允许编译器从主调函数的活跃帧分配内存,如果编译器可以证明协同程序的生存周期确实在主调函数的生存周期内严格嵌套的话。在许多情况下,只要有足够智能的编译器,这可以避免堆分配。

With coroutines there are some parts of the activation frame that need to be preserved across coroutine suspension and there are some parts that only need to be kept around while the coroutine is executing. For example, the lifetime of a variable with a scope that does not span any coroutine suspend-points can potentially be stored on the stack.

对于协程,活跃帧的有些部分需要在协程挂起的时候也要被保留,而另一些部分只需要在协程执行的时候保留。例如,生命周期不跨挂起点的变量将可能会被分配到栈上。

You can logically think of the activation frame of a coroutine as being comprised of two parts: the ‘coroutine frame’ and the ‘stack frame’.

你可以将活跃帧认为由逻辑上的两部分组成:“协程帧(coroutine frame)”和“栈帧(stack frame)”。

The ‘coroutine frame’ holds part of the coroutine’s activation frame that persists while the coroutine is suspended and the ‘stack frame’ part only exists while the coroutine is executing and is freed when the coroutine suspends and transfers execution back to the caller/resumer.

“协程帧”持有在协程挂起期间仍然存在的那部分协程活跃帧,而“栈帧”持有只在协程执行期间存在的那部分栈帧,“栈帧”的这部分内存会在协程挂起和将控制权交回给主调函数和调用Resume的函数时被释放。

The ‘Suspend’ operation

The Suspend operation of a coroutine allows the coroutine to suspend execution in the middle of the function and transfer execution back to the caller or resumer of the coroutine.

协程的“Suspend”操作允许协程从函数中间暂停执行,而转去执行主调函数或调用了Resume的函数。

There are certain points within the body of a coroutine that are designated as suspend-points. In the C++ Coroutines TS, these suspend-points are identified by usages of the co_await or co_yield keywords.

协程体中有一些点被指定为挂起点。在C++ Coroutines TS中,这些挂起点通过co_await或co_yield关键字来标识。

When a coroutine hits one of these suspend-points it first prepares the coroutine for resumption by:

  • Ensuring any values held in registers are written to the coroutine frame
  • Writing a value to the coroutine frame that indicates which suspend-point the coroutine is being suspended at.This allows a subsequent Resume operation to know where to resume execution of the coroutine or so a subsequent Destroy to know what values were in-scope and need to be destroyed.

当一个协程执行到这些挂起点时它首先通过以下操作准备将来的恢复:

  • 保证所有的寄存器值都被写入到协程帧中
  • 向协程帧写入一个值,该值指示该协程被挂起的挂起点。这允许后续的恢复操作知道在何处恢复执行协程程序,或者允许后续的销毁操作知道哪些值在作用域内,哪些值需要销毁。

Once the coroutine has been prepared for resumption, the coroutine is considered ‘suspended’.

一旦协程准备好了被恢复,那么该协程被认为处于“挂起”状态

The coroutine then has the opportunity to execute some additional logic before execution is transferred back to the caller/resumer. This additional logic is given access to a handle to the coroutine-frame that can be used to later resume or destroy it.

然后,协程有机会在执行权被转回调用者/恢复者之前执行一些额外的逻辑。这个额外的逻辑被赋予了对协程帧句柄的访问权,该句柄可用于以后恢复或销毁它。

This ability to execute logic after the coroutine enters the ‘suspended’ state allows the coroutine to be scheduled for resumption without the need for synchronisation that would otherwise be required if the coroutine was scheduled for resumption prior to entering the ‘suspended’ state due to the potential for suspension and resumption of the coroutine to race. I’ll go into this in more detail in future posts.

这种在协程进入“挂起”状态后执行逻辑的能力允许协程被安排为恢复,而不需要同步,如果协程在进入“挂起”状态之前被安排为恢复,那么则需要同步,以防止可能出现的挂起和恢复的竞态条件。我将在以后的帖子中更详细地讨论这个问题。

The coroutine can then choose to either immediately resume/continue execution of the coroutine or can choose to transfer execution back to the caller/resumer.

协程程序可以选择立即恢复/继续执行协程或者将执行权交回给主调函数/恢复者。

If execution is transferred to the caller/resumer the stack-frame part of the coroutine’s activation frame is freed and popped off the stack.

如果执行权被交回给主调函数/恢复者,那么协程活跃帧的栈帧部分将会被释放和出栈。

The ‘Resume’ operation

The Resume operation can be performed on a coroutine that is currently in the ‘suspended’ state.

可以在一个“挂起”状态的协程上执行Resume操作。

When a function wants to resume a coroutine it needs to effectively ‘call’ into the middle of a particular invocation of the function. The way the resumer identifies the particular invocation to resume is by calling the void resume() method on the coroutine-frame handle provided to the corresponding Suspend operation.

当一个函数要对协程执行resume操作时,它需要有效地“调用”协程函数内的代码。resumer要恢复的特定调用的方法是,对相应Suspend操作提供的协程帧句柄调用void resume() 方法。

Just like a normal function call, this call to resume() will allocate a new stack-frame and store the return-address of the caller in the stack-frame before transferring execution to the function.

就像一个一般的函数调用一样,对resume的调用也会分配一个新的栈帧,并且会在执行权交出之前将主调函数的返回地址存储在栈帧上。

However, instead of transferring execution to the start of the function it will transfer execution to the point in the function at which it was last suspended. It does this by loading the resume-point from the coroutine-frame and jumping to that point.

然而,resume方法并不会像一般执行函数那样跳转到被调函数的开始位置执行,它会转到协程程序被挂起时执行到的位置继续执行。它通过从协程帧中加载resume-point并且跳转到这个执行位置来实现这种(从函数中间开始执行)。

When the coroutine next suspends or runs to completion this call to resume() will return and resume execution of the calling function.

当协程程序下一次挂起或者完成时,这一次对resume的调用将会返回,然后将会转回继续执行(调用了resume的)主调函数。

The ‘Destroy’ operation

The Destroy operation destroys the coroutine frame without resuming execution of the coroutine.

“Destroy”操作将不通过恢复协程的执行而直接将其协程帧销毁掉。

This operation can only be performed on a suspended coroutine.

这个操作只能作用于一个挂起的协程上。

The Destroy operation acts much like the Resume operation in that it re-activates the coroutine’s activation frame, including allocating a new stack-frame and storing the return-address of the caller of the Destroy operation.

“Destroy”操作与“Resume”操作在某种意味上有一些相似之处,它也会激活协程的活跃帧,包括分配一个新的栈帧然后存储调用了“Destroy”的函数的返回地址。

However, instead of transferring execution to the coroutine body at the last suspend-point it instead transfers execution to an alternative code-path that calls the destructors of all local variables in-scope at the suspend-point before then freeing the memory used by the coroutine frame.

但是,它没有在最后一个挂起点将执行转移到协程体,而是将执行转移到另一个代码路径,该路径在挂起点调用作用域中所有局部变量的析构函数,然后释放协程帧使用的内存。

Similar to the Resume operation, the Destroy operation identifies the particular activation-frame to destroy by calling the void destroy() method on the coroutine-frame handle provided during the corresponding Suspend operation.

与Resume操作类似,Destroy操作通过在相应的Suspend操作期间提供的协程帧句柄上调用void Destroy()方法来销毁特定的活跃帧。

The ‘Call’ operation of a coroutine

The Call operation of a coroutine is much the same as the call operation of a normal function. In fact, from the perspective of the caller there is no difference.

协程程序的“调用”和一般函数的调用基本相同。实际上,从主调函数的角度两者没有差异。

However, rather than execution only returning to the caller when the function has run to completion, with a coroutine the call operation will instead resume execution of the caller when the coroutine reaches its first suspend-point.

然而,除了在函数执行到结尾时会返回执行主调函数外,协程程序也会在它执行到第一个挂起点时返回继续执行主调函数。

When performing the Call operation on a coroutine, the caller allocates a new stack-frame, writes the parameters to the stack-frame, writes the return-address to the stack-frame and transfers execution to the coroutine. This is exactly the same as calling a normal function.

当“调用”一个协程程序时,主调函数分配一个新的栈帧,将参数写入到栈帧上,将返回地址写入到栈帧上,然后转去执行协程程序。这与调用一个一般函数完全相同。

The first thing the coroutine does is then allocate a coroutine-frame on the heap and copy/move the parameters from the stack-frame into the coroutine-frame so that the lifetime of the parameters extends beyond the first suspend-point.

协程程序所做的第一件事就是在堆上分配一个协程帧,然后将栈帧上的数据复制或移动到协程帧上,这样这些数据的生存周期就可以延伸到第一个挂起点之后。

The ‘Return’ operation of a coroutine

The Return operation of a coroutine is a little different from that of a normal function.

协程程序的“返回”则与一般函数有所不同。

When a coroutine executes a return-statement (co_return according to the TS) operation it stores the return-value somewhere (exactly where this is stored can be customised by the coroutine) and then destructs any in-scope local variables (but not parameters).

当一个协程程序执行到return语句(在TS中是co_return语句)时,它将返回值存储在某一个地方(具体的位置要看不同编译器对协程的实现),之后将本地作用域内的变量全部销毁(不包括参数)。

The coroutine then has the opportunity to execute some additional logic before transferring execution back to the caller/resumer.

之后协程程序有一次在将执行权交回给主调函数/恢复函数之前执行一些额外逻辑的机会。

This additional logic might perform some operation to publish the return value, or it might resume another coroutine that was waiting for the result. It’s completely customisable.

可以通过额外逻辑来发布返回值,或者恢复另一个等待此执行结果的协程。这完全由你定制。

The coroutine then performs either a Suspend operation (keeping the coroutine-frame alive) or a Destroy operation (destroying the coroutine-frame).

然后协程可以执行“挂起”操作(保留协程帧)或者“销毁”操作(销毁协程帧)。

Execution is then transferred back to the caller/resumer as per the Suspend/Destroy operation semantics, popping the stack-frame component of the activation-frame off the stack.

然后根据Suspend/Destroy操作语义将执行权转移回调用者/恢复者,将活跃帧数据弹出堆栈。

It is important to note that the return-value passed to the Return operation is not the same as the return-value returned from a Call operation as the return operation may be executed long after the caller resumed from the initial Call operation.

需要强调的是,传递给“返回”操作的返回值与传递给“调用”操作的返回值不相同,因为返回操作可能在主调函数从初始的“调用”操作恢复执行后很长时间才被执行。

An illustration

To help put these concepts into pictures, I want to walk through a simple example of what happens when a coroutine is called, suspends and is later resumed.

为了更好地说明这些概念,我想串一个例子来说明当协程被调用、挂起和被恢复时发生了什么。

So let’s say we have a function (or coroutine), f() that calls a coroutine, x(int a).

假设我们有一个函数(或协程)f(),调用了另外一个协程x(int a)。

Before the call we have a situation that looks a bit like this:

在调用之前,情况可能如下图所示:
在这里插入图片描述
Then when x(42) is called, it first creates a stack frame for x(), as with normal functions.

然后当调用了x(42)时,像一般函数一样,它首先会为x()函数创建一个栈帧。

在这里插入图片描述
Then, once the coroutine x() has allocated memory for the coroutine frame on the heap and copied/moved parameter values into the coroutine frame we’ll end up with something that looks like the next diagram. Note that the compiler will typically hold the address of the coroutine frame in a separate register to the stack pointer (eg. MSVC stores this in the rbp register).

然后,一旦协程x()在堆上为协程帧分配了内存并且将参数值复制或移动到协程帧上,那么最终结果将会如下图所示。注意编译器将会将协程帧的地址放到一个与栈指针不同的寄存器内(在MSVC中它是rbg寄存器)。

在这里插入图片描述
If the coroutine x() then calls another normal function g() it will look something like this.

如果协程x()调用了另外的一个一般函数g(),那么情况就会变得有点像这样。

在这里插入图片描述
When g() returns it will destroy its activation frame and restore x()’s activation frame. Let’s say we save g()’s return value in a local variable b which is stored in the coroutine frame.

当g()返回时它将会销毁g()的活跃帧然后恢复x()的活跃帧。假设g()的返回值存储在活跃帧的局部变量b中。

在这里插入图片描述
If x() now hits a suspend-point and suspends execution without destroying its activation frame then execution returns to f().

如果此时x()执行到第一个挂起点并且暂停了执行,但它没有销毁变量,它将会将执行权交还给f()。

This results in the stack-frame part of x() being popped off the stack while leaving the coroutine-frame on the heap. When the coroutine suspends for the first time, a return-value is returned to the caller. This return value often holds a handle to the coroutine-frame that suspended that can be used to later resume it. When x() suspends it also stores the address of the resumption-point of x() in the coroutine frame (call it RP for resume-point).

这将会导致x()的栈帧部分出栈,但是它的协程帧部分仍然保存在堆上。当协程第一次挂起时,一个返回值将会返回给主调函数。这个返回值往往也持有一个协程帧挂起的句柄,这样以便于后面重新恢复它。当x()挂起时它也会将x()的恢复执行的地址存储到协程帧上(一般将恢复点简写为RP)。

在这里插入图片描述
This handle may now be passed around as a normal value between functions. At some point later, potentially from a different call-stack or even on a different thread, something (say, h()) will decide to resume execution of that coroutine. For example, when an async I/O operation completes.

这个句柄现在可以像一个一般的值一样在函数间传递。在之后的一些位置,可能是来自于不同的调用栈,甚至是不同的线程,假设是h()函数,决定恢复协程的执行。例如,这可能在一个异步I/O操作完成时出现。

The function that resumes the coroutine calls a void resume(handle) function to resume execution of the coroutine. To the caller, this looks just like any other normal call to a void-returning function with a single argument.

要恢复协程的函数通过调用 void resume(handle) 函数恢复了协程的执行。对于这个resume的主调函数,这次调用就像对一般的函数调用一样,调用了一个单参数的返回值类型为void的函数。

This creates a new stack-frame that records the return-address of the caller to resume(), activates the coroutine-frame by loading its address into a register and resumes execution of x() at the resume-point stored in the coroutine-frame.

这个操作创建了一个新的栈帧,这个栈帧记录了返回主调函数的地址以备后续返回,然后通过将被调函数x()的协程帧地址加载到寄存器中并开始执行协程帧中x()的恢复位置来激活协程帧。
在这里插入图片描述

In summary

I have described coroutines as being a generalisation of a function that has three additional operations - ‘Suspend’, ‘Resume’ and ‘Destroy’ - in addition to the ‘Call’ and ‘Return’ operations provided by “normal” functions.

这篇文章中我描述了由函数向协程扩展后的三个操作:“挂起”、“恢复”和“销毁”,以及“一般”函数的“调用”和“返回”操作。

I hope that this provides some useful mental framing for how to think of coroutines and their control-flow.

我希望这能为如何考虑协同过程及其控制流提供一些有用的思想框架。

In the next post I will go through the mechanics of the C++ Coroutines TS language extensions and explain how the compiler translates code that you write into coroutines.

在下一篇文章中,我将介绍C++ Coroutines TS语言扩展的机制,并解释编译器如何将编写的代码翻译成协程程序。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++程是一种轻量级的线程,可以在单个线程中实现多个程。C++20引入了程库,其中包括程原语和一些实用程序,使程的使用变得更加容易。下面是一个简单的C++程实现demo: ```c++ #include <iostream> #include <coroutine> struct Generator { struct promise_type { int current_value; Generator get_return_object() { return Generator(std::coroutine_handle<promise_type>::from_promise(*this)); } auto initial_suspend() { return std::suspend_always{}; } auto final_suspend() noexcept { return std::suspend_always{}; } void unhandled_exception() {} void return_void() {} auto yield_value(int value) { current_value = value; return std::suspend_always{}; } }; std::coroutine_handle<promise_type> coroutine; Generator(std::coroutine_handle<promise_type> h) : coroutine(h) {} ~Generator() { if (coroutine) coroutine.destroy(); } int get() { coroutine.resume(); return coroutine.promise().current_value; } }; Generator counter(int begin, int end) { for (int i = begin; i <= end; ++i) co_yield i; } int main() { auto gen = counter(1, 5); while (auto value = gen.get()) std::cout << value << ' '; } ``` 这个demo中,我们定义了一个生成器(Generator)结构体和一个promise_type结构体。Generator结构体包含一个程句柄,可以通过该句柄来操作程。promise_type结构体定义了生成器的类型,并提供了返回对象,挂起,终止等方法。在counter函数中,我们使用co_yield关键字来挂起当前程并返回值。在主函数中,我们使用while循环来不断调用程的get方法来获取生成的值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值