代码环境:python3.6

上一篇文章我们讲了 python 中多线程的使用:点击阅读,现在我们讲讲 python 中的协程。

异步IO

我们知道,CPU 速度远远快于磁盘、网络等 IO。在 IO 编程中,假如一个 IO 操作阻塞了当前线程,会导致其他代码无法执行,所以我们使用多线程或者多进程来并发执行代码。

但是,系统资源是有限的,一旦线程数量过多,CPU 的时间就花在线程切换上了,真正执行代码的时间下降,导致性能严重下降。

针对这个问题,我们需要另一种解决方法:异步 IO。

异步 IO,即当代码需要执行一个耗时的 IO 操作时,它只发出 IO 指令,并不等待 IO 结果,然后就去执行其他代码了。一段时间后,当 IO 返回结果时,再通知 CPU 进行处理。

python中最初的协程

了解最初的协程有助于我们理解后面现代协程的用法。

协程这个概念并不是 python 首次提出的,而是从其他语言借鉴过来的。

我们知道,两个普通函数的调用是按顺序的,比如A函数调用B函数,B执行完毕返回结果给A,A执行完毕。

协程看上去也是函数,如果协程A调用协程B,在执行过程中,协程B可以中断,转而执行A,再在适当的时候返回B接着从中断处往下执行。

协程的这种执行特点,恰好符合我们的需求:通过协程实现异步 IO 编程。

生成器进化成协程

python 基于 generator 进行一系列功能改进后得到协程,语法上都是定义体中包含 yield 关键字。

在协程中,yield 不仅可以返回值,还能接收调用者通过.send()方法发出的参数。yield 通常出现在表达式右边,如:data = yield something。如果 yield 后面没有表达式,说明此时 yield 只负责接收数据,协程始终返回None。

简单协程的基本行为

举个简单例子:

In [1]: def my_coroutine():

...: print('协程被激活')

...: while True:

# yield 后面不跟表达式,这里只接收 send() 传过来的数据

...: x = yield

...: print(f'协程接收到参数:{x}')

...:

In [2]: my_corou = my_coroutine()

# 可查看协程当前状态

In [3]: from inspect import getgeneratorstate

In [4]: getgeneratorstate(my_corou)

Out[4]: 'GEN_CREATED'

# 激活协程,此处可用 my_corou.send(None) 代替

In [5]: next(my_corou)

协程被激活

In [6]: getgeneratorstate(my_corou)

Out[6]: 'GEN_SUSPENDED'

In [7]: return_value = my_corou.send(99)

协程接收到参数:99

In [8]: print(return_value)

None

In [9]: my_corou.close()

In [10]: getgeneratorstate(my_corou)

Out[10]: 'GEN_CLOSED'

通过例子我们主要了解的是,协程需要手动激活才能真正调用,协程在不需要的时候要记得关闭。

用协程改进生产者-消费者模型

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

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

from inspect import getgeneratorstate

def consumer():

r = '200 OK'

while True:

# yield接收生产者的数据赋值给n,并把处理结果状态r返回

n = yield r

print(f'[CONSUMER] 消费了:{n}')

def producer(c):

# 别忘了激活协程

c.send(None)

n = 0

while n < 5:

n = n + 1

print(f'[PRODUCER] 生产了:{n}')

# 一旦生产了东西,通过c.send()切换到consumer执行

# consumer处理数据后通过yield返回结果状态,这里获取返回内容

r = c.send(n)

print(f'[PRODUCER] 消费者返回的处理结果:{r}')

print(f'生产者不生产了,看看当前consumer状态:{getgeneratorstate(c)}')

c.close()

print(f'关闭consumer,看看当前consumer状态:{getgeneratorstate(c)}')

if __name__ == "__main__":

producer(consumer())

上面例子整个流程只由一个线程执行且无锁,生产者和消费者协作完成任务,这种属于协作式多任务,跟多线程这种抢占式多任务要区分开。

asyncio

在 python3.4 版本中,开始引入标准库asyncio直接内置了对异步 IO 的支持。

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步 IO。

先简单介绍下asyncio涉及到的一些词语:

Future:一个对象,表示异步执行的操作。通常情况下自己不应该创建Future,而只能由并发框架如asyncio实例化。

Task:在EventLoop中负责执行协程的任务,是Future的子类。换句话说,Task就是Future,但反过来不一定。

下面是asyncio常用API:

asyncio.get_event_loop():获取一个EventLoop对象,用来运行协程

asyncio.iscoroutine(obj):判断一个对象是否是协程。

asyncio.sleep(delay):直接当做是一个耗时多少秒的协程即可。

asyncio.ensure_future(coro_or_future):入参是协程,则激活协程,返回一个Task对象;如果入参是Future,则将入参直接返回。

asyncio.gather(coros_or_futures):按入参中协程的顺序保存协程的执行结果,大部分情况下使用。

asyncio.wait(futures):对比gather,不一定按入参顺序返回执行结果。返回包含已完成和挂起的Task,可通过接收参数return_when选择返回结果的时机,按实际情况使用。

我们将在下面结合新的关键字async/await来举例说明。

async/await

为了简化使用和标识异步 IO,从 python3.5 版本开始引入新的语法糖async/await,用async把一个generator标记为协程函数,然后在协程内部用await调用另一个协程实现异步操作。

注意:

用async标记协程函数,调用该函数时协程尚未激活,激活该函数可以用await或者yield from,也可以通过ensure_future()或者AbstractEventLoop.create_task()调度执行。

举个例子:

from asyncio import sleep as aiosleep, gather, get_event_loop

async def compute(x, y):

print("计算 %s + %s ..." % (x, y))

await aiosleep(1)

return x + y

async def print_sum(x, y):

result = await compute(x, y)

print("%s + %s = %s" % (x, y, result))

async def coro_main():

'''一般我们会写一个 coroutine 的 main 函数,专门负责管理协程'''

await gather(print_sum(1, 2), print_sum(4, 9))

def main():

aioloop = get_event_loop()

# 内部使用ensure_future()激活协程

aioloop.run_until_complete(coro_main())

aioloop.close()

if __name__ == "__main__":

main()

执行结果:

计算 1 + 2 ...

计算 4 + 9 ...

(暂停约1秒,实际输出没有这行)

1 + 2 = 3

4 + 9 = 13

观察例子运行结果,我们看到:

当协程开始计算1+2前还有一个耗时 1 秒的 IO 操作,当前线程并未等待,而是去执行其他协程计算4+9,实现了并发执行。

协程结果按gather入参的顺序打印。

总结

面对 CPU 高速执行和 IO 设备的龟速严重不匹配问题,我们至少要知道两种解决方法:使用多进程和多线程并发执行代码;使用异步 IO 执行代码。

python 协程是基于生成器改进后得到的,底部实现都是定义体中包含yield关键字。

协程属于协作式多任务,整个流程无需锁,跟多线程这种抢占式多任务要区分开。

asyncio支持异步 IO,我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步 IO。

定义协程函数时,我们用async标记协程函数,然后在协程内部用await调用另一个协程实现异步操作。

python3 携程_多任务(3):协程相关推荐

  1. python爬虫02-提升爬取效率、多线程,多线程传参,多进程,线程及线程池概念,协程,多任务异步协程,异步请求aiohttp模块,视频站工作原理

    1.提升爬取效率 使用多线程,多进程,携程,异步 2.多线程 进程是资源单位,每个进程,都会有一个默认的主线程 线程是执行单位 执行多线程需要导包: from threading import Thr ...

  2. c++ 协程_理解Python协程(Coroutine)

    由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...

  3. kotlin协程_使Kotlin协程无缝采用的5个技巧

    kotlin协程 After successfully adopting coroutines in my prod project I think it is time to share 5 tip ...

  4. 进程 线程 协程_进程 线程 协程 管程 纤程 概念对比理解

    不知道是不是我自己本身就有那么一丝丝的密集恐惧,把这么一大堆看起来很相似很相关的概念放在一起,看起来是有点麻,捋一捋感觉舒服多了. 相关概念 任务.作业(Job,Task,Schedule) 在进程的 ...

  5. python爬虫 单线程的多任务异步协程

    在input().sleep(2).request.get()等时,都会导致线程阻塞,协程可以解决IO等操作时的阻塞现象,提高CPU利用效率. 1.单线程的多任务异步协程 main.py " ...

  6. 小爬爬4.协程基本用法多任务异步协程爬虫示例(大数据量)

    1.测试学习 (2)单线程: from time import sleep import time def request(url):print('正在请求:',url)sleep(2)print(' ...

  7. 爬虫的单线程+多任务异步协程:asyncio 3.6

    单线程+多任务异步协程:asyncio 3.6 事件循环 无限循环的对象.事件循环中最终需要将一些 特殊的函数(被async关键字修饰的函数) 注册在该对象中. 协程 本质上是一个对象.可以把协程对象 ...

  8. python爬虫 asyncio aiohttp aiofiles 单线程多任务异步协程爬取图片

    python爬虫 asyncio aiohttp aiofiles 多任务异步协程爬取图片 main.py """=== coding: UTF8 ==="&q ...

  9. php携程语比,PHP 协程

    理解生成器 参考官方文档:Generators 生成器让我们快速.简单地实现一个迭代器,而不需要创建一个实现了Iterator接口的类后,再实例化出一个对象. 一个生成器长什么样?如下 1 2 3 4 ...

最新文章

  1. EI:天大王灿+昆士兰郭建华揭示生物气溶胶是猪场耐药基因的重要传播途径
  2. 图像传感器与信号处理——详解图像传感器噪声
  3. Nauuo and Circle
  4. 逻辑漏洞小结之SRC篇
  5. Codevs 1025 选菜
  6. 热式气体质量流量计检定规程_热式气体质量流量计基于热扩散原理
  7. 地铁线路图的设计与实现
  8. 微信小程序----相对路径图片不显示
  9. php 依赖注入 数据库切换_通俗易懂理解PHP依赖注入容器
  10. Bernoulli-Gaussian分布
  11. 爬取东方财富股吧评论
  12. php中美元符号是什么意思,js程序中美元符号$是什么
  13. 从入门到放弃系列-傅里叶变换,拉普拉斯变换,Z变换
  14. 大数据趣味学习探讨(二):我是怎么坚持学习的
  15. [概率论]图像里的“白噪声”——电视机搜不到台时雪花斑点的形成原因 (不信谣,不传谣,与宇宙微波背景辐射没有任何关系)
  16. 同方向只有一条机动车道的道路
  17. idea断点调试,调试结束后,下次无法进入断点
  18. http://blog.sina.com.cn/s/blog_5da93c8f0102w86x.html
  19. 2003服务器 临时文件,#Excel自动保存在哪#office 2003未保存的临时文件在哪个目录里?...
  20. ml1220纽扣电池供电时长

热门文章

  1. Linux 两台服务器之间传输文件和文件夹
  2. C++基础01-C++对c的增强
  3. 在Linux系统下实现进程,Linux进程学习(一)之Linux进程的基本知识和实现
  4. Jmeter Beanshell采样器调用JAVA方法(二)
  5. 【Python】这些Python骚操作,你值得拥有
  6. 并发队列、线程池、锁
  7. 深入了解Kubernetes REST API的工作方式
  8. 使用exp导出导入,需要注意的问题。
  9. 论如何入门地使用vscode
  10. linux 端口号查看