Python asyncio之协程学习总结 1

简介: Python asyncio之协程学习总结

实践环境

Python 3.6.2

什么是协程

协程(Coroutine)一种电脑程序组件,该程序组件通过允许暂停和恢复任务,为非抢占式多任务生成子程序。协程也可以简单理解为协作的程序,通过协同多任务处理实现并发的函数的变种(一种可以支持中断的函数)。

下面,我们通过日常生活场景为例,对什么是协程进行说明。

假设A某在家每天都要做3件事:洗衣服(使用洗衣机),蒸饭(使用电饭煲),扫地(使用扫地机器人),这三样电器在完成任务后都会发出不一样响声来告诉A某事情已经完成。

这里,暂且假设A某智商有问题,每次都是严格按顺序做这三件事:先洗完衣服,再把饭蒸好,最后才开始扫地。

接下来,我们用一段简单的代码来模拟上述整个过程,并记录整个过程的耗时,其中使用了3个简单的普通函数,分别模拟上述3件事情,如下:

import time
from datetime import datetime
def do_washing():
    print(datetime.now(), ':开始洗衣服')
    time.sleep(3)  # 洗衣服 # 用程序休眠来模拟过程,且别计较时间大小
    print(datetime.now(), ':通知A某衣服洗好了') 
def steame_rice():
    print(datetime.now(), ':开始蒸饭')
    time.sleep(5)  # 蒸饭
    print(datetime.now(), ':通知A某饭蒸好了')
def do_clearing():
    print(datetime.now(), ':开始扫地')
    time.sleep(2)  # 扫地
    print(datetime.now(), ':通知A某地扫完了')
if __name__ == '__main__':
    startTime = time.time()
    do_washing()
    steame_rice()
    do_clearing()
    endTime = time.time()
    print("扫地+蒸饭+洗衣服总耗时: ", endTime - startTime)

程序输出:

2023-04-09 23:33:50.001204 :开始洗衣服
2023-04-09 23:33:53.002765 :衣服洗好了
2023-04-09 23:33:53.002765 :开始蒸饭
2023-04-09 23:33:58.013337 :通知A某饭蒸好了
2023-04-09 23:33:58.013337 :通知A某开始扫地
2023-04-09 23:34:00.024784 :通知A某地扫完了
扫地+蒸饭+洗衣服总耗时:  10.023579835891724

直到有一天,A某的朋友来他家做客,体验到他的“高效”办事效率后,建议他不用等每件事情都做完才做下一件事情。A某听后,虚心采纳,并告诉自己要开始培养新的习惯。

第二天开始呢,A某开始改变自己,把衣服扔洗衣机,并启动机洗程序后,就去淘米蒸饭了,等电饭煲开始蒸饭后,就去清扫地板了。

接下来,我们对上述代码进行稍微修改,以便模拟上述过程,并记录整个过程的耗时,如下:

import time
from datetime import datetime
import asyncio
async def do_washing():
    print(datetime.now(),':开始洗衣服')
    await asyncio.sleep(3)
    print(datetime.now(),':通知A某衣服洗好了')
async def do_clearing():
    print(datetime.now(), ':开始扫地')
    await asyncio.sleep(5)
    print(datetime.now(), ':通知A某地扫完了')
async def steame_rice():
    print(datetime.now(), ':开始蒸饭')
    await asyncio.sleep(2)
    print(datetime.now(), ':通知A某饭蒸好了')
tasks = [
    do_washing(),
    steame_rice(),
    do_clearing()
]
if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    start_time = time.time()
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
    end_time = time.time()
    print("扫地+蒸饭+洗衣服总耗时: ", end_time - start_time)

程序输出:

2023-04-09 23:35:17.422790 :开始扫地
2023-04-09 23:35:17.422790 :开始蒸饭
2023-04-09 23:35:17.422790 :开始洗衣服
2023-04-09 23:35:19.427500 :通知A某饭蒸好了
2023-04-09 23:35:20.427813 :通知A某衣服洗好了
2023-04-09 23:35:22.429780 :通知A某地扫完了
扫地+蒸饭+洗衣服总耗时:  5.0069899559021

不得不夸A某进步真大,相比之前,这次耗时减少了近一半。

以上这段代码就是协程的简单实现,充分体现了协程的3个特点:

  1. 多任务并行:A某同时完成了3项任务--分别代表3个协程。
  2. 异步任务:3项任务中,没有一项是需要A某在一旁一直看着直到做完的,每项任务开启后,A某都可以离开去做别的任务。
  3. 协作式(非抢占式):每项任务能否“占用”A某,取决于A某是否正被其它任务“占用”,即是否有任务主动“让出”A某,不是靠“抢占”,更像是协商。

有了线程为啥还要协程?

协程是用户视角的一种抽象,操作系统并没有这个概念,其主要思想是在用户态实现调度算法,用少量线程完成大量任务的调度。

相对线程而言,协程具备以下优势:

  • 减少内存占用
    协程的创建成本远小于线程,可以设计得很小,小到KB级别,大大降低内存占用。所以,内存资源有限的情况下,可以创建更多协程,从而实现更高的并发。
  • 减少上下文切换开销,节约CPU资源

如上图,线程之间的切换请求,由系统内核来实现,而协程之间的切换,则可由用户自由控制,即交由用户态的代码来完成,极大程度避免了系统内核级线程上下文切换造成的CPU资源浪费。具体实现思路如下:

  1. 尽量减少可执行的线程,这样切换次数必然会少
  2. 让线程尽可能的处于运行状态,而不是阻塞让出时间片

一个线程可以拥有多个协程,主要注意的是,一个线程内的多个协程却是串行的,无论CPU有多少个核,因为协程本质上还是一个函数,当一个协程运行时,其它协程必须挂起。实际开发过程中,可以使用协程在将一些耗时的IO操作异步化,例如写文件、耗时IO请求等来提升程序执行效率。

相关语法说明

接下来,就上面的例子,对协程相关语法进行说明。

async def do_washing()

使用async def语法定义协程函数do_washing

协程函数示例:

async def func(param1, param2):
    do_stuff()
    await some_coroutine()

注意:

  1. 使用async def语法定义的函数始终是协程函数,即使它们不包含waitasync关键字。
  2. 采用传统的函数调用方式,直接调用协程函数,函数不会被立即执行,会产生类似RuntimeWarning: coroutine 'xxxx协程函数' was never awaited的告警日志,并返回一个协程对象。仅运行事件循环时才会运行协程。
  3. await挂起当前协程以等待一个可等待(awaitable)对象--协程函数或者实现了__await__()的对象,直到可等待对象返回结果。可以将这个可等待对象,简单的理解为待执行的异步任务(一般是比较耗时的任务,比如开篇示例中用作比拟的煲饭)。注意:
  1. await只能在协程函数内部使用。
  2. 程序遇到await关键词时,会将程序控制权交给主程序,由主程序分配给其它协程。当可等待对象返回结果,并且此时程序控制权还被其它协程占用时,则被挂起的协程依旧无法继续往下运行,直到获取程序控制权。关于这个结论,可用下述示例代码进行验证:
from datetime import datetime
import asyncio
async def do_washing():
    print(datetime.now(),':开始洗衣服')
    await asyncio.sleep(0.5)
    for i in range(10000):
        if i % 4000 == 0:
            print('洗衣服')
    print(datetime.now(),':衣服洗好了')
async def do_cooking():
    print(datetime.now(), ':开始煲饭')
    for i in range(100000):
        if i%20000 == 0:
            print('煲饭')
    await asyncio.sleep(5)
    print(datetime.now(), ':饭煲好了')
tasks = [
    do_cooking(),
    do_washing()
]
if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
  1. 输出:
2023-04-10 23:53:37.804727 :开始洗衣服
2023-04-10 23:53:37.804727 :开始煲饭
煲饭
煲饭
煲饭
煲饭
煲饭
洗衣服
洗衣服
洗衣服
2023-04-10 23:53:38.310586 :衣服洗好了
2023-04-10 23:53:42.811876 :饭煲好了
asyncio.sleep(2)

给定秒数后完成的协程--阻塞指定的秒数。sleep函数还可以指定result参数,协程完成时将该参数值返回给调用者(默认返回None),如下:

result = await asyncio.sleep(0.5, result='task done')
print(result) # 输出:task done

sleep总是会挂起当前任务,以允许其他任务运行。可以利用这个特性,将秒数设置为0,即asyncio.sleep(0),以便提供一个经优化的路径以允许其他任务运行。 这可供长时间运行的函数使用,避免调用该函数时阻塞事件循环。

asyncio.get_event_loop()

为当前上下文获取事件循环(event loop),返回一个实现了AbstractEventLoop接口的事件循环对象。如果没有为当前上下文设置任何事件循环,且当前策略没有指定创建一个事件循环,则抛出异常。必须返回非None值。

AbstractEventLoop.run_until_complete(asyncio.wait(tasks))

运行直到asyncio.wait(tasks)运行完成。返回asyncio.wait(tasks)的运行结果,或者抛出异常。

asyncio.run(coro, *, debug=False)

执行协程 coro 并返回结果。

此函数会运行传入的协程,负责管理 asyncio 事件循环,终结异步生成器,并关闭线程池。

当有其他 asyncio 事件循环在同一线程中运行时,此函数不能被调用。

如果debugTrue,事件循环将以调试模式运行。

此函数总是会创建一个新的事件循环并在结束时关闭之。它应当被用作 asyncio 程序的主入口点,理想情况下应当只被调用一次。

示例:

async def main():
    await asyncio.sleep(1)
    print('hello')
asyncio.run(main())

3.7 新版功能.

asyncio.wait(tasks)

具备完整参数列表的wait函数定义如下

asyncio.wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

并发地运行 fs可迭代对象中的可等待对象,并进入阻塞状态直到满足return_when参数所指定的条件(缺省参值为ALL_COMPLETED)。

注意,aws参数不能为空。

函数返回 Future 集合: (done, pending)

请注意,此函数不会引发 asyncio.TimeoutError。当超时发生时,未完成的 Future 将在指定秒数后被返回。

return_when 指定此函数应在何时返回,可选值如下:

  • FIRST_COMPLETED
    函数将在任意可等待对象结束或取消时返回。
  • FIRST_EXCEPTION
    函数将在任意可等待对象因引发异常而结束时返回。当没有引发任何异常时它就相当于ALL_COMPLETED
  • ALL_COMPLETED
    函数将在所有可等待对象结束或取消时返回。
目录
相关文章
|
20天前
|
Python
Python中的异步编程:使用asyncio和aiohttp实现高效网络请求
【10月更文挑战第34天】在Python的世界里,异步编程是提高效率的利器。本文将带你了解如何使用asyncio和aiohttp库来编写高效的网络请求代码。我们将通过一个简单的示例来展示如何利用这些工具来并发地处理多个网络请求,从而提高程序的整体性能。准备好让你的Python代码飞起来吧!
44 2
|
24天前
|
调度 开发者 Python
Python中的异步编程:理解asyncio库
在Python的世界里,异步编程是一种高效处理I/O密集型任务的方法。本文将深入探讨Python的asyncio库,它是实现异步编程的核心。我们将从asyncio的基本概念出发,逐步解析事件循环、协程、任务和期货的概念,并通过实例展示如何使用asyncio来编写异步代码。不同于传统的同步编程,异步编程能够让程序在等待I/O操作完成时释放资源去处理其他任务,从而提高程序的整体效率和响应速度。
|
20天前
|
数据库 Python
异步编程不再难!Python asyncio库实战,让你的代码流畅如丝!
在编程中,随着应用复杂度的提升,对并发和异步处理的需求日益增长。Python的asyncio库通过async和await关键字,简化了异步编程,使其变得流畅高效。本文将通过实战示例,介绍异步编程的基本概念、如何使用asyncio编写异步代码以及处理多个异步任务的方法,帮助你掌握异步编程技巧,提高代码性能。
53 4
|
20天前
|
API 数据处理 Python
探秘Python并发新世界:asyncio库,让你的代码并发更优雅!
在Python编程中,随着网络应用和数据处理需求的增长,并发编程变得愈发重要。asyncio库作为Python 3.4及以上版本的标准库,以其简洁的API和强大的异步编程能力,成为提升性能和优化资源利用的关键工具。本文介绍了asyncio的基本概念、异步函数的定义与使用、并发控制和资源管理等核心功能,通过具体示例展示了如何高效地编写并发代码。
30 2
|
1月前
|
调度 开发者 Python
异步编程在Python中的应用:Asyncio和Coroutines
异步编程在Python中的应用:Asyncio和Coroutines
21 1
|
2月前
|
关系型数据库 MySQL 数据处理
探索Python中的异步编程:从asyncio到异步数据库操作
在这个快节奏的技术世界里,效率和性能是关键。本文将带你深入Python的异步编程世界,从基础的asyncio库开始,逐步探索到异步数据库操作的高级应用。我们将一起揭开异步编程的神秘面纱,探索它如何帮助我们提升应用程序的性能和响应速度。
|
2月前
|
调度 Python
python知识点100篇系列(20)-python协程与异步编程asyncio
【10月更文挑战第8天】协程(Coroutine)是一种用户态内的上下文切换技术,通过单线程实现代码块间的切换执行。Python中实现协程的方法包括yield、asyncio模块及async/await关键字。其中,async/await结合asyncio模块可更便捷地编写和管理协程,支持异步IO操作,提高程序并发性能。协程函数、协程对象、Task对象等是其核心概念。
|
2月前
|
调度 开发者 Python
异步编程在Python中的应用:Asyncio和Coroutines
【10月更文挑战第12天】本文介绍了Python中的异步编程,重点讲解了`asyncio`模块和协程的概念、原理及使用方法。通过异步编程,程序可以在等待I/O操作时继续执行其他任务,提高整体效率。文章还提供了一个简单的HTTP服务器示例,展示了如何使用`asyncio`和协程编写高效的异步代码。
20 2
|
2月前
|
机器学习/深度学习 人工智能 架构师
Python学习圣经:从0到1,精通Python使用
尼恩架构团队的大模型《LLM大模型学习圣经》是一个系统化的学习系列,初步规划包括以下内容: 1. **《Python学习圣经:从0到1精通Python,打好AI基础》** 2. **《LLM大模型学习圣经:从0到1吃透Transformer技术底座》**
Python学习圣经:从0到1,精通Python使用
|
28天前
|
NoSQL 关系型数据库 MySQL
python协程+异步总结!
本文介绍了Python中的协程、asyncio模块以及异步编程的相关知识。首先解释了协程的概念和实现方法,包括greenlet、yield关键字、asyncio装饰器和async/await关键字。接着详细讲解了协程的意义和应用场景,如提高IO密集型任务的性能。文章还介绍了事件循环、Task对象、Future对象等核心概念,并提供了多个实战案例,包括异步Redis、MySQL操作、FastAPI框架和异步爬虫。最后提到了uvloop作为asyncio的高性能替代方案。通过这些内容,读者可以全面了解和掌握Python中的异步编程技术。
43 0