对协程的一点理解

什么是协程

A coroutine is a function that can suspend its execution (yield) until the given YieldInstruction finishes.

也就是说,协程是一个函数,可以被挂起和被恢复。

协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程那样需要上下文切换来消耗资源,因此协程的开销远远小于线程的开销。

子程序

子程序,就是函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:

def A():
    print '1'
    print '2'
    print '3'

def B():
    print 'x'
    print 'y'
    print 'z'

假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:

1
2
x
y
3
z

但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。

看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那

和多线程比,协程有何优势?

1. 切换开销小,执行效率高

因为子程序切换不是线程切换,而是由程序自身控制

2. 不需要多线程的锁机制

因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了

子程序与协程的差异:

协程可以通过yield(取其“让步”之义而非“出产”)来调用其它协程,接下来的每次协程被调用时,从协程上次yield返回的位置接着执行,通过yield方式转移执行权的协程之间不是调用者与被调用者的关系,而是彼此对称、平等的

1. 子程序可以调用其他子程序,调用者等待被调用者结束后继续执行,故而子程序的生命期遵循后进先出,即最后一个被调用的子例程最先结束返回。协程的生命期完全由对它们的使用需要来决定。(也就是想怎么跳就怎么跳,不需要遵循函数调用那套规则)

2. 子程序的起始处是惟一的入口点,而协程可以有多个入口点,协程的起始处是第一个入口点,每个yield返回出口点都是再次被调用执行时的入口点。

3. 子例程只在结束时一次性的返回全部结果值。协程可以在yield时不调用其他协程,而是每次返回一部分的结果值,这种协程常称为生成器迭代器

子例程是协程的特里,因为任何子例程都可以看作是不调用yield的协程

示例

这里是一个简单的例子证明协程的实用性。

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

var q := 新建队列

coroutine 生产者
    loop
        while q 不满载
            建立某些新产品
            向 q 增加这些产品 
        yield 给消费者

coroutine 消费者
    loop
        while q 不空载
            从 q 移除某些产品
            使用这些产品
        yield 给生产者

应用场景

根据今天查阅的资料来看,协程的应用场景主要在于 :I/O 密集型任务。

当程序在执行 I/O 时操作时,时间片的算法并不知道,就分配了时间片给他,此时CPU 是空闲的,因此可以充分利用 CPU 的时间片来处理其他任务。
- 在单线程中,一个函数调用,一般是从函数的第一行代码开始执行,结束于 return 语句、异常或者函数执行(也可以认为是隐式地返回了 None )。(相当于被阻塞了,CPU就被浪费了)
- 有了协程,我们在函数的执行过程中,如果遇到了耗时的 I/O 操作,函数可以临时让出控制权,让 CPU 执行其他函数,等 I/O 操作执行完毕以后再收回控制权。(相当于暂时跳过去)

参考链接

posted @ 2020-03-19 22:29  Rogn  阅读(606)  评论(0编辑  收藏  举报