关于我

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

Github:https://github.com/hylinux1024

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

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

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

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

  1. def producer(c):
  2. n = 0
  3. while n < 5:
  4. n += 1
  5. print('producer {}'.format(n))
  6. r = c.send(n)
  7. print('consumer return {}'.format(r))
  8. def consumer():
  9. r = ''
  10. while True:
  11. n = yield r
  12. if not n:
  13. return
  14. print('consumer {} '.format(n))
  15. r = 'ok'
  16. if __name__ == '__main__':
  17. c = consumer()
  18. next(c) # 启动consumer
  19. producer(c)

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

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

0x00 何为协程(Coroutine)

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

定义一个协程

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

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

  1. import asyncio
  2. # 定义了一个简单的协程
  3. async def simple_async():
  4. print('hello')
  5. await asyncio.sleep(1) # 休眠1秒
  6. print('python')
  7. # 使用asynio中run方法运行一个协程
  8. asyncio.run(simple_async())
  9. # 执行结果为
  10. # hello
  11. # python

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

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

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

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

  1. async def do_something(index):
  2. print(f'start {time.strftime("%X")}', index)
  3. await asyncio.sleep(1)
  4. print(f'finished at {time.strftime("%X")}', index)
  5. def test_do_something():
  6. # 生成器产生多个协程对象
  7. task = [do_something(i) for i in range(5)]
  8. # 获取一个事件循环对象
  9. loop = asyncio.get_event_loop()
  10. # 在事件循环中执行task列表
  11. loop.run_until_complete(asyncio.wait(task))
  12. loop.close()
  13. test_do_something()
  14. # 运行结果
  15. # start 00:04:03 3
  16. # start 00:04:03 4
  17. # start 00:04:03 1
  18. # start 00:04:03 2
  19. # start 00:04:03 0
  20. # finished at 00:04:04 3
  21. # finished at 00:04:04 4
  22. # finished at 00:04:04 1
  23. # finished at 00:04:04 2
  24. # finished at 00:04:04 0

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

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

0x01 Awaitable对象

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

  1. await awaitable_object
1. 协程(Coroutine)

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

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

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

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

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

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

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

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

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

3. Future

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

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

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

  1. async def used_future_func():
  2. await function_that_returns_a_future_object()

0x02 并发

1. Task

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

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

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

2. gather

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

  1. async def do_after(what, delay):
  2. await asyncio.sleep(delay)
  3. print(what)
  4. async def gather():
  5. print(f'started at {time.strftime("%X")}')
  6. # 使用gather可将多个协程传入
  7. await asyncio.gather(
  8. do_after('hello', 1),
  9. do_after('python', 2),
  10. )
  11. print(f'finished at {time.strftime("%X")}')
  12. asyncio.run(gather())
  13. # 运行结果
  14. # started at 23:47:50
  15. # hello
  16. # python
  17. # 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. nu.xom:Serializer

    Serializer: 机翻 /* 使用用于控制空格,规范化,缩进,换行和基本URI的各种选项以特定编码输出Document对象 */ Serializer(OutputStream out) :创建 ...

  2. ZIP:ZipEntry

    ZipEntry: /* 此类用于表示 ZIP 文件条目. */ ZipEntry(String name) :使用指定名称创建新的 ZIP 条目. ZipEntry(ZipEntry e) :使用从 ...

  3. CSDN怎么一键转载别人的博客

    在参考"如何快速转载CSDN中的博客"后,由于自己不懂html以及markdown相关知识,所以花了一些时间来弄明白怎么转载博客,以下为转载CSDN博客步骤和一些知识小笔记. 参考 ...

  4. cozmo 入坑日记及开发环境搭建

    前几日,朋友在群里发了一个机器人的小视频,视频里机器人可以对话,可以推箱子,开心以后会哈哈大笑,非常有趣. 详细了解里一下,这是个叫 cozmo 的智能机器人,可以配合 SDK 用 python 编程 ...

  5. 个人永久性免费-Excel催化剂功能第55波-Excel批注相关的批量删除作者、提取所有批注信息等

    Excel里的批注,许多人很喜欢用,但批注真的值得我们大量使用吗?批注的使用场景在哪里?这些问题可能更值得花时间来思考下.同样因为不规范地使用批注,也带出了一大堆的后续擦屁股的事情来,从批注中找回有价 ...

  6. NPM介绍

    惠善一的博客:https://huishanyi.club NPM(Node Package Manger),Node包管理工具.在安装完Node之后,NPM便已经同时安装完成,用户可以通过NPM将自 ...

  7. Android 异常 UncaughtException detected: java.lang.RuntimeException: Parcelable encountered IOExcepti

    异常信息: UncaughtException detected: java.lang.RuntimeException: Parcelable encountered IOException wri ...

  8. C#2.0新增功能07 getter/setter 单独可访问性

    连载目录    [已更新最新开发文章,点击查看详细] 属性是一种成员,它提供灵活的机制来读取.写入或计算私有字段的值. 属性可用作公共数据成员,但它们实际上是称为访问器的特殊方法. 这使得可以轻松访问 ...

  9. 浏览器和Node 中的Event Loop

    前言 js与生俱来的就是单线程无阻塞的脚本语言. 作为单线程语言,js代码执行时都只有一个主线程执行任务. 无阻塞的实现依赖于我们要谈的事件循环.eventloop的规范是真的苦涩难懂,仅仅要理解的话 ...

  10. 一文了解有趣的位运算(&、|、^、~、>>、<<)

    1.位运算概述 从现代计算机中所有的数据二进制的形式存储在设备中.即0.1两种状态,计算机对二进制数据进行的运算(+.-.*./)都是叫位运算,即将符号位共同参与运算的运算. 口说无凭,举一个简单的例 ...