Python笔记四之协程
本文首发于公众号:Hunter后端
原文链接:Python笔记四之协程
协程是一种运行在单线程下的并发编程模型,它的特点是能够在一个线程内实现多个任务的并发操作,通过在执行任务时主动让出执行权,让其他任务继续执行,从而实现并发。
以下所有的代码都是在 Python 3.8 版本中运行。
本篇笔记目录如下:
- asyncio
async
await - 并发运行协程任务
- 获取协程返回结果
- asyncio.gather()
- 报错处理
- 超时处理
- 用协程的方式访问网络接口
1、asyncio
在 Python 中,协程使用 asyncio 模块来实现,asyncio 是用来编写并发代码的库,使用的 async/await 语法。
async
我们使用 async 做前缀将普通函数变成异步函数,比如:
import asyncio
import time
async def say_after(delay, what):
now = time.time()
await asyncio.sleep(delay)
print(what, " 花时间:", time.time() - now)
return time.time(
async def main():
print("started at: ", time.strftime("%X"))
await say_after(1, "hello")
await say_after(2, "world")
print("finished at: ", time.strftime("%X"))
asyncio.run(main())
函数前加上 async 就将其变成了一个异步函数,在这里我们通过 asyncio.run() 的方式在外层调用异步函数。
await
在 main() 函数里,我们通过 await 的方式表示在异步函数,也就是 main 函数里暂停当前的操作,等待后面跟着的 say_after() 异步函数执行完成。
await say_after() 就是我们前面说过的在执行任务的时候主动让出执行权,让其他任务执行。
2、并发运行协程任务
在上面 main() 函数的两个 await say_after() 中,可以看到两次 print() 出来的时间差约为 3s,因为我们两次调用 say_after() 分别用了 1 秒和 2 秒时间,所以这两次 await 操作是暂停当前任务的串行执行。
如果我们想要实现协程的并发操作,可以使用 asyncio.create_task()
async def main():
task1 = asyncio.create_task(say_after(1, "hello"))
task2 = asyncio.create_task(say_after(2, "hello"))
print("started at: ", time.strftime("%X"))
await task1
await task2
print("finished at: ", time.strftime("%X"))
asyncio.run(main())
# started at: 11:40:03
# hello 花时间: 1.0013182163238525
# hello 花时间: 2.001201868057251
# finished at: 11:40:05
在 say_after() 函数中,有一个 await asyncio.sleep() 的操作,它的作用是在协程中主动挂起当前任务一段时间,并将控制权返回给事件循环,允许其他协程继续执行。
它模拟了在协程中等待一定时间的行为,比如在协程中发起网络请求后,协程会挂起等待网络请求的响应返回,或者异步 IO 操作中的等待 IO 操作完成等。
所以在上面这个函数操作中,我们通过 asyncio.create_task() 将协程函数 say_after() 添加到事件循环中进行自动调度,并在合适的时机执行。
所以在上面的操作中,程序检测到 say_after() 中需要进行 sleep 的操作,就会自动对其进行调度,切换到事件循环的下一个任务执行,这样就实现了协程任务的并发操作。
也因此,程序执行的整体时间会比前面的操作快 1 秒左右。
获取协程返回结果
协程的返回结果直接在 await 前赋值即可:
result1 = await task1
print(result1)
asyncio.gather()
asyncio.gather() 也可以用于并发执行协程任务,但是与 asyncio.create_task() 略有不同。
在 create_task() 的操作是将协程函数添加到事件循环中进行调度,返回的是一个 Task 对象,而 gather() 则可以直接接收多个协程任务并发执行,并等待他们全部完成,返回 Future 对象表示任务结果。
gather() 的使用方法如下:
async def main():
results = await asyncio.gather(
say_after(1, "hello"),
say_after(2, "world"),
)
asyncio.gather() 除了可以接收异步函数,还可以接受 asyncio.create_task() 返回的结果,也就是返回的 task 对象,比如下面的操作也是合法的:
async def main():
task = asyncio.create_task(say_after(1, "hello"))
results = await asyncio.gather(
say_after(1, "hello"),
say_after(2, "world"),
task,
)
3、报错处理
如果在并发操作中有一些报错,比如下面的示例:
import asyncio
import time
async def say_after(delay, what):
now = time.time()
await asyncio.sleep(delay)
print(what, " 花时间:", time.time() - now)
return time.time()
async def say_error(delay, err_msg="error"):
await asyncio.sleep(delay)
raise Exception(err_msg
async def main():
results = await asyncio.gather(
say_after(1, "hello"),
say_error(2, "error"),
say_after(3, "world"),
)
print(results)
asyncio.run(main())
在上面的操作中,三个协程函数,在执行到第二个的时候,程序其实就直接返回报错了,如果想要忽略报错继续执行之后的操作,可以加上 return_exceptions 参数,设为 True:
async def main():
results = await asyncio.gather(
say_after(1, "hello"),
say_error(2, "error"),
say_after(3, "world"),
return_exceptions=True,
)
print(result)
# [1691045418.774685, Exception('error'), 1691045420.774549]
这样就会将报错信息直接也返回,且执行之后的协程函数。
4、超时处理
我们可以为协程函数执行的时间预设一个时间,如果超出这个时间则返回报错信息,我们可以使用 asyncio.wait_for(),比如:
async def main_4():
results = await asyncio.gather(
say_after(1, "hello"),
say_error(2, "error"),
asyncio.wait_for(say_after(30, "world"), timeout=3),
return_exceptions=True,
)
print(results)
# [1691045925.265661, Exception('error'), TimeoutError()]
在上面的操作中,我们给第三个任务加了个 3 秒的超时处理,但是该协程会执行 30 秒,所以返回的报错里是一个 TimeoutError()。
5、用协程的方式访问网络接口
接下来我们用协程的方式来访问一个接口,与不用协程的方式进行比对。
首先我们建立一个服务端,用 Django、Flask都可以,只是提供一个访问接口,以下是用 Flask 建立的示例:
from flask import Flask
import time
def create_app():
app = Flask(__name__)
@app.route("/test")
def test():
time.sleep(1)
return str(time.time())
return app
运行这段代码就提供了我们需要的服务器接口。
使用协程的方式访问接口我们这里用到的是 aiohttp,是第三方库,需要提前安装:
pip3 install aiohttp==3.8.5
进行测试的脚本如下:
import asyncio
import aiohttp
import requests
import time
CALL_TIMES = 10000
def connect_url(url):
return requests.get(url)
def run_connect_url(url):
results = []
for i in range(CALL_TIMES):
result = connect_url(url)
results.append(result)
return results
async def connect_url_by_session(session, url):
async with session.get(url) as response:
return await response.text()
async def run_connect(url):
async with aiohttp.ClientSession() as session:
tasks = []
for i in range(CALL_TIMES):
tasks.append(connect_url_by_session(session, url))
results = await asyncio.gather(*tasks)
return results
if __name__ == "__main__":
url = "http://127.0.0.1:5000/test"
t1 = time.time()
run_connect_url(url)
print(f"串行调用次数: {CALL_TIMES},耗时:{time.time() - t1}")
t2 = time.time()
asyncio.run(run_connect(url))
print(f"协程调用次数:{CALL_TIMES},耗时:{time.time() - t2}")
在这里,aiohttp 的具体用法看代码即可,我们可以通过修改 CALL_TIMES 来修改调用次数,我这里调用 1000 次和 10000 次的结果分别如下:
串行调用次数: 1000,耗时:3.2450389862060547
协程调用次数:1000,耗时:1.3642120361328125
串行调用次数: 10000,耗时:32.830286741256714
协程调用次数:10000,耗时:12.519049882888794
可以看到使用协程的方式对于接口的访问效率有了明显的提升。
如果想获取更多相关文章,可扫码关注阅读:

Python笔记四之协程的更多相关文章
- python进阶——进程/线程/协程
1 python线程 python中Threading模块用于提供线程相关的操作,线程是应用程序中执行的最小单元. #!/usr/bin/env python # -*- coding:utf-8 - ...
- 四 python并发编程之协程
一 引子 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两种情况下切走去 ...
- python 并发专题(十二):基础部分补充(四)协程
相关概念: 协程:一个线程并发的处理任务 串行:一个线程执行一个任务,执行完毕之后,执行下一个任务 并行:多个CPU执行多个任务,4个CPU执行4个任务 并发:一个CPU执行多个任务,看起来像是同时执 ...
- python tornado TCPserver异步协程实例
项目所用知识点 tornado socket tcpserver 协程 异步 tornado tcpserver源码抛析 在tornado的tcpserver文件中,实现了TCPServer这个类,他 ...
- python——asyncio模块实现协程、异步编程
我们都知道,现在的服务器开发对于IO调度的优先级控制权已经不再依靠系统,都希望采用协程的方式实现高效的并发任务,如js.lua等在异步协程方面都做的很强大. Python在3.4版本也加入了协程的概念 ...
- 32 python 并发编程之协程
一 引子 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两种情况下切走去 ...
- Python使用gevent实现协程
Python中多任务的实现可以使用进程和线程,也可以使用协程. 一.协程介绍 协程,又称微线程.英文名Coroutine.协程是Python语言中所特有的,在其他语言中没有. 协程是python中另外 ...
- Python程序中的协程操作-gevent模块
目录 一.安装 二.Gevent模块介绍 2.1 用法介绍 2.2 例:遇到io主动切换 2.3 查看threading.current_thread().getName() 三.Gevent之同步与 ...
- 31、Python程序中的协程操作(greenlet\gevent模块)
一.协程介绍 协程:是单线程下的并发,又称微线程,纤程.英文名Coroutine.一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的. 对比操作系统控制线程的切换,用 ...
- Python爬虫进阶 | 异步协程
一.背景 之前爬虫使用的是requests+多线程/多进程,后来随着前几天的深入了解,才发现,对于爬虫来说,真正的瓶颈并不是CPU的处理速度,而是对于网页抓取时候的往返时间,因为如果采用request ...
随机推荐
- Solon 生态插件清单
Solon 是一个微型的Java开发框架.强调,克制 + 简洁 + 开放的原则:力求,更小.更快.更自由的体验.支持:RPC.REST API.MVC.Micro service.WebSocket. ...
- SQL Server 事务执行、回滚
MySQL 事务回滚.在执行删除.更新等操作时,防止误操作 生产环境更新数据时必用 begin tran --开启事务 begin--先在事务中 执行 UPDATE Sys_User SET Name ...
- AudioLDM 2,加速!
AudioLDM 2 由刘濠赫等人在 AudioLDM 2: Learning Holistic Audio Generation with Self-supervised Pretraining 一 ...
- Python上下文管理器的高级使用
在文件处理和网络编程时,对于打开的文件不管最后内容处理是否符合预期都要在结束时关闭文件.这时常见的处理方法是try catch finally 的方法 f = open("demo.txt& ...
- Java | 个人学习指南笔记
前言:由于作者已经有C语言,C++和Python语言的基础了,所以在文章的编写时会以这几门编程语言作对比.本文学习自 C语言中文网的 Java 教程,部分内容引用自这.引用内容仅作学习使用. 第1章: ...
- 如虎添翼!高德地图+Serverless 护航你的假日出行
作者 | 刘金龙(福辰) 高德团队 引言 "前方事故多发地段,请注意保持车距..." "您已疲劳驾驶,请注意休息..." "前方经过泰山旅游景 ...
- 人人都是 Serverless 架构师 | 弹幕应用开发实战
作者 | 寒斜(阿里云云原生中间件前端负责人) 如何使用 Serverless 架构实现全双工通信的应用,Serverless 架构中数据库是如何使用的,本篇文章将为您揭开答案. Serverl ...
- vue学习笔记 七、方法的定义和使用
系列导航 vue学习笔记 一.环境搭建 vue学习笔记 二.环境搭建+项目创建 vue学习笔记 三.文件和目录结构 vue学习笔记 四.定义组件(组件基本结构) vue学习笔记 五.创建子组件实例 v ...
- uniapp#实现自定义省市区三级联动
uni-APP中的三级联动(省市区)---数据前端写死 https://blog.csdn.net/lwaner/article/details/107150805 uniapp#实现自定义省市区三级 ...
- vue prop 会接收不同的数据类型
refAge: { type: Number, default: 0 }, refName: { type: String, default: '' }, hotDataLoading: { type ...