Python3 协程相关

什么是协程

  协程(Coroutine),又称微线程,纤程。一般咱们认为线程是轻量级的进程,所以咱们也把协程理解为轻量级的线程即微线程。python

  协程的做用是在执行函数A时能够随时中断去执行函数B,而后中断函数B继续执行函数A(能够自由切换)。这里的中断,不是函数的调用,而是有点相似CPU的中断。这一整个过程看似像多线程,然而协程只有一个线程执行。多线程

协程的优点

  • 执行效率极高,由于是子程序(函数)切换不是线程切换,由程序自身控制,没有切换线程的开销。因此与多线程相比,线程的数量越多,协程的性能优点越明显。
  • 不须要机制,由于只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不须要加锁,只须要判断状态,所以执行效率高的多。
  • 协程能够处理IO密集型程序的效率问题,但不适合处理CPU密集型程序,如要充分发挥CPU利用率应结合多进程+协程。

Python3中的协程

生成器 yield/send

yield + send(利用生成器实现协程)

例:并发

def simple_coroutine():
    print('-> start')
    x = yield
    print(x)
    print('-> end')

#主线程
sc = simple_coroutine()
# 可使用sc.send(None),效果同样
next(sc) #预激
sc.send('go')

执行结果以下:异步

-> start
go
-> end

抛出StopIteration
  • simple_coroutine()是一个生成器,由next(sc) 预激,启动协程,执行到第一个yield中断
  • send()方法给yield传入参数,继续执行async

    协程的四个状态

  • GEN_CREATED:等待开始执行
  • GEN_RUNNING:解释器正在执行
  • GEN_SUSPENED:在yield表达式处暂停
  • GEN_CLOSED:执行结束函数

    协程终止

  • 协程中未处理的异常会向上冒泡,传给 next 函数或 send 方法的调用方(即触发协程的对象)
  • 终止协程的一种方式:发送某个哨符值,让协程退出。内置的 None 和Ellipsis 等常量常常用做哨符值oop

@asyncio.coroutine和yield from

asyncio.coroutione

  asyncio是Python3.4版本引入的标准库,直接内置了对异步IO的支持。asyncio的异步操做,须要在coroutine中经过yield from完成。
例:性能

import asyncio
@asyncio.coroutine
def test(i):
    print('test_1', i)
    r = yield from asyncio.sleep(1)
    print('test_2', i)

loop = asyncio.get_event_loop()
tasks = [test(i) for i in range(1,3)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
  • @asyncio.coroutine把一个generator标记为coroutine类型,而后就把这个coroutine扔到EventLoop中执行。test()会首先打印出test_1
  • 而后yield from语法可让咱们方便地调用另外一个generator。因为asyncio.sleep()也是一个coroutine,因此线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。
  • asyncio.sleep()返回时,线程就能够从yield from拿到返回值(此处是None)
  • 而后接着执行下一行语句。把asyncio.sleep(1)当作是一个耗时1秒的IO操做,在此期间主线程并未等待,而是去执行EventLoop中其余能够执行的coroutine了,所以能够实现并发执行

结果以下:线程

test_1 1
test_1 2
test_2 1
test_2 2

yield from

  当yield from 后面加上一个生成器后,就实现了生成的嵌套。
  实现生成器的嵌套,并非必定必需要使用yield from,而是使用yield from可让咱们避免让咱们本身处理各类料想不到的异常,而让咱们专一于业务代码的实现。
  若是本身用yield去实现,那只会加大代码的编写难度,下降开发效率,下降代码的可读性。code

  • 调用方:调用委派生成器的客户端(调用方)代码
  • 委托生成器:包含yield from表达式的生成器函数
  • 子生成器:yield from后面加的生成器函数
# 子生成器
def average_gen():
    total = 0
    count = 0
    average = 0
    while True:
        new_num = yield average
        count += 1
        total += new_num
        average = total/count

# 委托生成器
def proxy_gen():
    while True:
        yield from average_gen()

# 调用方
def main():
    calc_average = proxy_gen()
    next(calc_average)# 预激下生成器
    print(calc_average.send(10))  
    print(calc_average.send(20))  
    print(calc_average.send(30))  

main()

结果以下:

10.0
15.0
20.0

委托生成器的做用:
在调用方与子生成器之间创建一个双向通道。

双向通道就是调用方能够经过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。

为何要用yield from

  yield from帮咱们作了不少的异常处理,而这些若是咱们要本身去实现的话,一个是编写代码难度增长,写出来的代码可读性不好,极可能有遗漏,只要哪一个异常没考虑到,都有可能致使程序崩溃。

async/await关键字

  为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可让coroutine的代码更简洁易读。

可将上面的案例代码改造以下:

import asyncio
# @asyncio.coroutine
async def test(i):
    print('test_1', i)
    # r = yield from asyncio.sleep(1)
    await asyncio.sleep(1)
    print('test_2', i)

loop = asyncio.get_event_loop()
tasks = [test(i) for i in range(1,3)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

执行结果

test_1 1
test_1 2
test_2 1
test_2 2
  • async 替换 @asyncio.coroutine,加在def以前用于修饰
  • await替换yield from
相关文章
相关标签/搜索