什么是协程

协程个人理解是相当于单个线程下的异步IO, 当程序运行到协程的时候,不等待结果返回直接向后执行,等IO 操作结束后,返回结果通知到消息队列,再做之后的操作。
1.4 更新
阅读 Linux Kernel Development 发现协程只存在用户态的一种形式,不对内核态暴露。这样就大大减少了,用户态和内核态切换的开销。有的语言设计,将协程的栈存放在堆中,切换的时候,直接从堆中读取,连上下文切换都节省了。

Python 协程

1. 协程函数

async 可以来声明协程函数。
await 只能用在 async 函数内部, await 的下一步操作会等待协程操作完成返回后在继续执行。
注意:协程函数必须添加到事件循环中注册,然后由事件循环去管理运行。

2. 运行协程, asyncio.run(func_)

这个 run 函数总是会创建一个新的事件循环并在 run 结束之后关闭事件循环,所以,如果在同一个线程中已经有了一个事件循环,则不能再使用这个函数了,因为同一个线程不能有两个事件循环,而且这个 run 函数不能同时运行两次,因为他已经创建一个了。即同一个线程中是不允许有多个事件循环 loop 的。如 Jupyter 中内置有事件循环,要使用 nest-asyncio 这个包来解决这个问题,允许嵌套使用 事件循环。

!pip install nest_asyncio
import nest_asyncio 
nest_asyncio.apply()

事件循环:线程一直在各个协程方法之间永不停歇的游走,遇到一个yield from 或者await就悬挂起来,然后又走到另外一个方法,依次进行下去,直到事件循环所有的方法执行完毕。

Task 任务是一个可等待对象, Task用来 并发调度的协程,即对协程函数的进一步包装?那为什么还需要包装呢?因为单纯的协程函数仅仅是一个函数而已,将其包装成任务,任务是可以包含各种状态的,异步编程最重要的就是对异步操作状态的把控了。
使用 task = asyncio.create_task(coro()) 来创建任务。

举个例子:

import asyncio
import nest_asyncio

nest_asyncio.apply()
 
async def worker_1():
    print('worker_1 start')
    await asyncio.sleep(1)
    print('worker_1 done')
 
async def worker_2():
    print('worker_2 start')
    await asyncio.sleep(2)
    print('worker_2 done')
 
async def main():
    task1 = asyncio.create_task(worker_1())
    task2 = asyncio.create_task(worker_2())
    print('before await')
    await task1
    print('awaited worker_1')
    await task2
    print('awaited worker_2')

    
%time asyncio.run(main())

返回

before await
worker_1 start
worker_2 start
worker_1 done
awaited worker_1
worker_2 done
awaited worker_2
CPU times: user 2.27 ms, sys: 3.97 ms, total: 6.24 ms
Wall time: 2 s

注意到,worker_1 的任务是在做完后,才执行 await 之后的内容。总的时间 2秒钟,而不是 2+1 =3 秒,通过 create_task 声明协程,运行到就会挂起,两个任务分别执行,相当于两个IO 是并发执行,所以总时间只用 2秒。

之前项目实战

将爬取每个视频下的每一页设为子任务(一个协程)。

多进程 vs 多线程 vs 协程

数据量: 332830 条评论
多进程 运行时长:1分2秒980毫秒
多线程 运行时长: 1分6秒651毫秒
协程 运行时长: 52秒411毫秒
分析:
这个任务主要是典型的爬虫项目,网络 IO比较多,并不能有效的利用多进程计算的多核CPU,所以多进程和多线程并没有明显差距。
本来以为 Python 的假多线程会跟单线程速度一样,结果去查了下,在 I/O阻塞时会释放锁,所以计算还是只能利用单核资源(GIL锁),但是因为 网络IO 阻塞可以模拟出 多线程的效果。

协程因为不需要切换上下文,栈空间小。速度要快一些。

I am a real pikachu!