本文将mark下协程(Coroutines)相关notes。

1. 基本概念

1.1 Why?

什么是协程

vs多线程:
操作系统在线程等待IO的时候,会阻塞当前线程,切换到其它线程,这样在当前线程等待IO的过程中,其它线程可以继续执行。当系统线程较少的时候没有什么问题,但是当线程数量非常多的时候,却产生了问题。一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间

协程刚好可以解决上述2个问题。协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

1.2 What

协程本质上和单线程+状态机是等价的,只是用协程的话,协程负责来保存状态,开发起来方便些(不用自己写那个状态机)。

1.3 When

在有大量IO操作业务的情况下,我们采用协程替换线程,可以到达很好的效果,一是降低了系统内存,二是减少了系统切换开销,因此系统的性能也会提升。

在协程中尽量不要调用阻塞IO的方法,比如打印,读取文件,Socket接口等,除非改为异步调用的方式,并且协程只有在IO密集型的任务中才会发挥作用。

协程只有和异步IO结合起来才能发挥出最大的威力

2. QEMU中的协程

2.1 为什么qemu要使用协程

Coroutines for better asynchronous programming

仔细阅读Coroutines in QEMU: The basics Callback hell in event-driven programs即可。The coroutine version is much easier to understand because the code is sequential. Under the hood the coroutine version returns back to the event loop just like the callback version. Therefore the code still uses the event loop but it can be written like a sequential program.

Coroutines make it possible to write sequential code that is actually executed across multiple iterations of the event loop. This is useful for code that needs to perform blocking I/O and would quickly become messy if split into a chain of callback functions.

2.2 The QEMU coroutine API

The coroutine API is documented in include/qemu/coroutine.h. The main functions are:

2.2.1 create coroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Coroutine entry point
*
* When the coroutine is entered for the first time, opaque is passed in as an
* argument.
*
* When this function returns, the coroutine is destroyed automatically and
* execution continues in the caller who last entered the coroutine.
*/
typedef void coroutine_fn CoroutineEntry(void *opaque);

/**
* Create a new coroutine
*
* Use qemu_coroutine_enter() to actually transfer control to the coroutine.
* The opaque argument is passed as the argument to the entry point.
*/
Coroutine *qemu_coroutine_create(CoroutineEntry *entry, void *opaque);

When a new coroutine is started, it will begin executing the entry function. The caller can pass an opaque pointer to data needed by the coroutine.

2.2.2 execute coroutine

The new coroutine is executed by calling qemu_coroutine_enter:

1
2
3
4
/**
* Transfer control to a coroutine
*/
void qemu_coroutine_enter(Coroutine *coroutine);

2.2.3 yield coroutine

If the coroutine needs to wait for an event such as I/O completion or user input, it calls qemu_coroutine_yield:

1
2
3
4
5
6
7
/**
* Transfer control back to a coroutine's caller
*
* This function does not return until the coroutine is re-entered using
* qemu_coroutine_enter().
*/
void coroutine_fn qemu_coroutine_yield(void);

The yield function transfers control back to the qemu_coroutine_enter caller. The coroutine can be re-entered at a later point in time by calling qemu_coroutine_enter, for example, when an I/O request has completed.


参考资料:

  1. Coroutines in QEMU: The basics
  2. QEMU中的协程—qemu-coroutine
  3. 什么是协程?
  4. ​浅谈协程
  5. 当谈论协程时,我们在谈论什么