关于我

一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。

Github:https://github.com/hylinux1024

微信公众号:终身开发者(angrycode)

在前一篇《一文彻底搞懂Python可迭代(Iterable)、迭代器(Iterator)和生成器(Generator)的概念》 的文中,知道生成器(Generator)可由以下两种方式定义:

  • 列表生成器
  • 使用yield定义的函数

Python早期的版本中协程也是通过生成器来实现的,也就是基于生成器的协程(Generator-based Coroutines)。在前一篇介绍生成器的文章末尾举了一个生产者-消费者的例子,就是基于生成器的协程来实现的。

def producer(c):
n = 0
while n < 5:
n += 1
print('producer {}'.format(n))
r = c.send(n)
print('consumer return {}'.format(r)) def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('consumer {} '.format(n))
r = 'ok' if __name__ == '__main__':
c = consumer()
next(c) # 启动consumer
producer(c)

看了这段代码,相信很多初学者和我一样对基于生成器的协程实现其实很难马上就能够根据业务写出自己的协程代码。Python实现者们也注意到这个问题,因为它太不Pythonic了。而基于生成器的协程也将被废弃,因此本文将重点介绍asyncio包的使用,以及涉及到的一些相关类概念。

注:我使用的Python环境是3.7。

0x00 何为协程(Coroutine)

协程(Coroutine)是在线程中执行的,可理解为微线程,但协程的切换没有上下文的消耗,它比线程更加轻量些。一个协程可以随时中断自己让另一个协程开始执行,也可以从中断处恢复并继续执行,它们之间的调度是由程序员来控制的(可以看本文开篇处生产者-消费者的代码)。

定义一个协程

Python3.5+版本新增了aysncawait关键字,这两个语法糖让我们非常方便地定义和使用协程。

在函数定义时用async声明就定义了一个协程。

import asyncio

# 定义了一个简单的协程
async def simple_async():
print('hello')
await asyncio.sleep(1) # 休眠1秒
print('python') # 使用asynio中run方法运行一个协程
asyncio.run(simple_async()) # 执行结果为
# hello
# python

在协程中如果要调用另一个协程就使用await要注意await关键字要在async定义的函数中使用,而反过来async函数可以不出现await

# 定义了一个简单的协程
async def simple_async():
print('hello') asyncio.run(simple_async()) # 执行结果
# hello

asyncio.run()将运行传入的协程,负责管理asyncio事件循环。

除了run()方法可直接执行协程外,还可以使用事件循环loop

async def do_something(index):
print(f'start {time.strftime("%X")}', index)
await asyncio.sleep(1)
print(f'finished at {time.strftime("%X")}', index) def test_do_something():
# 生成器产生多个协程对象
task = [do_something(i) for i in range(5)] # 获取一个事件循环对象
loop = asyncio.get_event_loop()
# 在事件循环中执行task列表
loop.run_until_complete(asyncio.wait(task))
loop.close() test_do_something() # 运行结果
# start 00:04:03 3
# start 00:04:03 4
# start 00:04:03 1
# start 00:04:03 2
# start 00:04:03 0
# finished at 00:04:04 3
# finished at 00:04:04 4
# finished at 00:04:04 1
# finished at 00:04:04 2
# finished at 00:04:04 0

可以看出几乎同时启动了所有的协程。

其实翻阅源码可知asyncio.run()的实现也是封装了loop对象及其调用。而asyncio.run()每次都会创建一个新的事件循环对象用于执行协程。

0x01 Awaitable对象

Python中可等待(Awaitable)对象有:协程(corountine)、任务(Task)、Future。即这些对象可以使用await关键字进行调用

await awaitable_object
1. 协程(Coroutine)

协程由async def声明定义,一个协程可由另一个协程使用await进行调用

async def nested():
print('in nested func')
return 13 async def outer(): # 要使用await 关键字 才会执行一个协程函数返回的协程对象
print(await nested()) asyncio.run(outer()) # 执行结果
# in nested func
# 13

如果在outer()方法中直接调用nested()而不使用await,将抛出一个RuntimeWarning

async def outer():
# 直接调用协程函数不会发生执行,只是返回一个 coroutine 对象
nested() asyncio.run(outer())

运行程序,控制台将输出以下信息

RuntimeWarning: coroutine 'nested' was never awaited
nested()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
2. 任务(Task)

任务(Task)是可以用来并发地执行协程。可以使用asyncio.create_task() 将一个协程对象封装成任务,该任务将很快被排入调度队列并执行。

async def nested():
print('in nested func')
return 13 async def create_task():
# create_task 将一个协程对象打包成一个 任务时,该协程就会被自动调度运行
task = asyncio.create_task(nested())
# 如果要看到task的执行结果
# 可以使用await等待协程执行完成,并返回结果
ret = await task
print(f'nested return {ret}') asyncio.run(create_task()) # 运行结果
# in nested func
# nested return 13

注:关于并发下文还会详细说明。

3. Future

Future是一种特殊的低层级(low-level)对象,它是异步操作的最终结果(eventual result)。

当一个 Future 对象 被等待,这意味着协程将保持等待直到该 Future 对象在其他地方操作完毕。

通常在应用层代码不会直接创建Future对象。在某些库和asyncio模块中的会使用到该对象。

async def used_future_func():
await function_that_returns_a_future_object()

0x02 并发

1. Task

前面我们知道Task可以并发地执行。 asyncio.create_task()就是一个把协程封装成Task的方法。

async def do_after(what, delay):
await asyncio.sleep(delay)
print(what) # 利用asyncio.create_task创建并行任务
async def corun():
task1 = asyncio.create_task(do_after('hello', 1)) # 模拟执行1秒的任务
task2 = asyncio.create_task(do_after('python', 2)) # 模拟执行2秒的任务 print(f'started at {time.strftime("%X")}')
# 等待两个任务都完成,两个任务是并行的,所以总时间两个任务中最大的执行时间
await task1
await task2 print(f'finished at {time.strftime("%X")}') asyncio.run(corun()) # 运行结果
# started at 23:41:08
# hello
# python
# finished at 23:41:10

task1是一个执行1秒的任务,task2是一个执行2秒的任务,两个任务并发的执行,总共消耗2秒。

2. gather

除了使用asyncio.create_task()外还可以使用asyncio.gather(),这个方法接收协程参数列表

async def do_after(what, delay):
await asyncio.sleep(delay)
print(what) async def gather():
print(f'started at {time.strftime("%X")}')
# 使用gather可将多个协程传入
await asyncio.gather(
do_after('hello', 1),
do_after('python', 2),
)
print(f'finished at {time.strftime("%X")}') asyncio.run(gather()) # 运行结果
# started at 23:47:50
# hello
# python
# finished at 23:47:52

两个任务消耗的时间为其中消耗时间最长的任务。

0x03 引用

  1. https://docs.python.org/3/library/asyncio-task.html

为何你还不懂得如何使用Python协程的更多相关文章

  1. day-5 python协程与I/O编程深入浅出

    基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1.  什么是协程(以下内容来自维基百 ...

  2. 从yield 到yield from再到python协程

    yield 关键字 def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b yield 是在:PEP 255 -- Simple Generator ...

  3. 关于python协程中aiorwlock 使用问题

    最近工作中多个项目都开始用asyncio aiohttp aiomysql aioredis ,其实也是更好的用python的协程,但是使用的过程中也是遇到了很多问题,最近遇到的就是 关于aiorwl ...

  4. [转载] Python协程从零开始到放弃

    Python协程从零开始到放弃 Web安全 作者:美丽联合安全MLSRC   2017-10-09  3,973   Author: lightless@Meili-inc Date: 2017100 ...

  5. 00.用 yield 实现 Python 协程

    来源:Python与数据分析 链接: https://mp.weixin.qq.com/s/GrU6C-x4K0WBNPYNJBCrMw 什么是协程 引用官方的说法: 协程是一种用户态的轻量级线程,协 ...

  6. python协程详解

    目录 python协程详解 一.什么是协程 二.了解协程的过程 1.yield工作原理 2.预激协程的装饰器 3.终止协程和异常处理 4.让协程返回值 5.yield from的使用 6.yield ...

  7. Python协程与Go协程的区别二

    写在前面 世界是复杂的,每一种思想都是为了解决某些现实问题而简化成的模型,想解决就得先面对,面对就需要选择角度,角度决定了模型的质量, 喜欢此UP主汤质看本质的哲学科普,其中简洁又不失细节的介绍了人类 ...

  8. python协程详解,gevent asyncio

    python协程详解,gevent asyncio 新建模板小书匠 #协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1.生成器实现切换 [1] ...

  9. Python核心技术与实战——十六|Python协程

    我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...

随机推荐

  1. v8环境搭建采坑记录

    项目组有把js接入C++服务求的需求,故开始了v8接入的工作,用了一天多时间,v8才在centos环境上成功安装,过程中踩了很多坑,下面将采坑过程记录如下: centos下编译安装v8:   查看ce ...

  2. WordPress教程之页面、菜单、媒体库、高级定制

    本系列教程链接: 怎么快速搭建一个WordPress网站 Wordpress教程之初识WordPress Wordpress教程之如何入门WordPress Wordpress教程之如何创建博客内容 ...

  3. UEditor 之初体验后记

    1.UEditor 基本介绍 1.1.关于 UEditor 1.2.UEditor 现状 2.UEditor 简单使用 2.1.将 UEditor 源码集成到项目中 2.2.让 UEditor 的 U ...

  4. 个人永久性免费-Excel催化剂功能第91波-地图数据挖宝之行政区域信息实时下载(含经纬度)

    移动互联网和O2O兴起的这十年时间里,由地图LBS功能衍生出一大堆的极高商业价值的数据及应用,地图相关的数据,也是数据分析过程中一个大宝藏,从此篇开始将带给大家一系列的地图相关的数据采集,满足数据分析 ...

  5. 关于python3与python2同时存在情况下导入pyqt失败解决记录

    最近感觉tkinter功能还是比较不适合新手做出高大上的界面,故开始使用pyqt,通过pip安装好了之后,利用qt设计师设计好界面之后,cmd运行之,报错提示没有找到pyqt5模块,IDE运行能正常加 ...

  6. 基于Bitnami gitlab OVA包的gitlab 环境搭建

    前言 最近在折腾gitlab,本篇记录搭建的过程方便以后查找 环境 Windows server + VMware 安装 为方便本次我们直接采用Bitnami的VOA安装包(VOA格式可同时兼容Vir ...

  7. 做dede网站知识点总结(捷斯网站)

    网站标题: {dede:global.cfg_webname/} 模板路径(引用js和css时候的路径): {dede:global.cfg_templets_skin/} 首页导航栏标签: < ...

  8. 上车时机已到--.NETCore是适应时代发展的雄鹰利剑

    上车时机已到--.NETCore是适应时代发展的雄鹰利剑 要起飞了 .NET Core 3.0-prevew7:https://dotnet.microsoft.com/download/dotnet ...

  9. 原创:微信小程序开发要点总结

    废话不多少,下面是对我从开发微信小程序的第一步开始到发布的总结,觉得对您有帮助的话,可以赞赏下,以对我表示鼓励. 一:首先注册登录微信公众平台,这个平台很重要,以后查文档全在上面看.https://m ...

  10. FormLayout and FormData

    FormLayout通过为小窗口部件创建四边的Form附加值(attachment)来进行工作,并且把这些Form附加值存储在布局数据中.一个附加值让一个小窗口部件指定的一边粘贴(attach)到父C ...