Python从使用线程到使用async/await的深入讲解
作者:临书 发布时间:2021-07-26 10:56:11
前言
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。
请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:
把@asyncio.rotoutine替换为async;
把yield from替换为await。
async/await 是一种异步变成方法,还有两种你可能听过,
1. 回调
2. Promise
(写过 JavaScript 的肯定很熟悉了)
异步意味着任务不会阻塞,比如,如果我要下载一个比较忙的网络资源,我的程序不需要一直等待下载完成,它可以在等待下载时继续做其他事情。这与并行执行多个操作不同。以下伪代码比较容易理解:
# 慢方法
page = get_page_sync('some_page')
# 会阻塞整个程序的运行
print(page)
有两种方法可以改善上述的情况
(一)首先,让我们试试使用线程。通过使用线程,我们可以将 get_page_sync 调用放到单独的线程去执行,这样主线程 就可以继续执行其他操作。
# 将慢方法放到单独的线程执行
t = threading.thread(
target = get_page_sync('some_page',args=('some_page',))
)
t.run()
# 在线程运行时执行其他操作
do_something_else()
# 等待线程完执行成
t.join()
线程有几个优缺点,主要的缺点是:
1. 必须在改变共享数据前锁定共享数据
2. 只能通过传递给主线程消息来处理线程内的异常
(二)现在我们试试第二种中的 async/await,Python3.5 开始支持的 async/await 方式,与第一种(线程)之间的主要区别在于,后者是操作系统内核执行上下文切换,而前者中我们自己控制。(上下文切换即,当多个线程正在运行时,内核可能停止当前进程,使其进入休眠状态,并选择不同的线程继续执行。这被称作抢占式多任务处理【Preemption】)
当我们自己控制时,它被称作非抢占式或合作型多任务式,因为是我们自己处理上下文切换,所以我们需要一个调度程序,也叫做『事件循环』。此事件循环只循环遍历等待中的调度,并运行它的所有事件。每当我们产生操作时,当前任务会被添加到队列中,且第一个任务(优先级而非顺序)从队列中弹出并开始执行。例如,可以通过以下方式更改上述伪代码:
async def print_page():
page = await get_page_sync('some_page')
print(page)
当我们触发上面的语句时,get_page_async 方法将非阻塞的获取 some_page 还有 yield 句柄,这意味着我们的 print_page 函数将控制时间循环 ,并且时间循环可以继续执行其他曹组,知道我们得到返回的响应。
我们先将我们的线程代码改造成这种语法。我们将使用 asyncio(Python 自带的时间循环库),并使用 aiohttp 包来执行异步 http 请求。
我们将会创建一个名为 main 函数,它将成为我们异步代码的入口。然后我们创建一个时间循环和一个「未来对象」。这个未来对象是对异步函数的抽象,它存储了一些基本的属性,比如它当前的状态(就像 Promise 一样) 。然后我们将告诉我们的时间循环继续运行,知道这个「未来」完成。
loop = asyncio.get_event_loop()
future = asyncio.ensure_future(main())
loop.run_until_complete(future)
在我们的 main 方法中,我们将创建另一个未来任务列表,每个任务负责从某网站下载不同的桐乡。我们这样做是因为每次下载都会发起网络请求,在网络请求时,我们可以运行另一端代码。创建任务列表后,我们可以通过调用等待整个列表执行完成 asyncio.gather ,这就是它的实现:
async def main():
tasks = []
async with aiohttp.ClientSession() as session:
for img in img_list:
task = asyncio.ensure_future(download_img(img, session))
task.append(task)
await asyncio.gather(*tasks)
(这段代码来的有点猛了)
最后一个我们要改的方法就是 download_img 了,我们仅仅需要替换 requests.get 调用为异步:
i = 1
async def download_img(img, session):
global i, bar
# 获取文件后缀
file_ext = get_extention(img.link)
# 拼接文件名
file_name = img.id + file_ext
resp = await session.get(img.link)
with open(file_name, 'wb') as f:
async for chunk in resp.content.iter_chunked(1024):
f.write(chunk)
bar.update(i)
i += 1
要注意的一点是在更新 i 的时候不需要先锁住它,这是因为我们前面说过,没有代码是同时执行的,所以永远不可能出现竞态条件。
因为没有锁或者线程的开销,异步版本可能还会比多线程版本快一些。
这是完整代码:
#! /usr/bin/env python
import os
import re
import sys
import aiohttp
import asyncio
import async_timeout
import progressbar
from imgurpython import ImgurClient
regex = re.compile(r'\.(\w+)$')
def get_extension(link):
ext = regex.search(link).group()
return ext
i = 1
async def download_img(img, session):
global i, bar
# get the file extension
file_ext = get_extension(img.link)
# create unique name by combining file id with its extension
file_name = img.id + file_ext
resp = await session.get(img.link)
with open(file_name, 'wb') as f:
async for chunk in resp.content.iter_chunked(1024):
f.write(chunk)
bar.update(i)
i += 1
try:
album_id = sys.argv[1]
except IndexError:
raise Exception('Please specify an album id')
client_id = os.getenv('IMGUR_CLIENT_ID')
client_secret = os.getenv('IMGUR_CLIENT_SECRET')
client = ImgurClient(client_id, client_secret)
img_lst = client.get_album_images(album_id)
bar = progressbar.ProgressBar(max_value=len(img_lst))
async def main():
tasks = []
async with aiohttp.ClientSession() as session:
for img in img_lst:
task = asyncio.ensure_future(download_img(img, session))
tasks.append(task)
await asyncio.gather(*tasks)
loop = asyncio.get_event_loop()
future = asyncio.ensure_future(main())
loop.run_until_complete(future)
原文:https://medium.com/@exqu17/python-bits-moving-from-threads-to-async-await-741ec5124cdc
作者:https://medium.com/@exqu17?source=post_header_lockup
来源:https://zhuanlan.zhihu.com/p/44661314


猜你喜欢
- 使用mysql_udf与curl库完成http_post通信模块(mysql_udf,multi_curl,http,post)这个模块其目
- 本文实例为大家分享了mysql 8.0.27 安装配置图文教程的具体代码,供大家参考,具体内容如下下载官网下载安装包:>MySQL :
- Golang与python线程详解及简单实例在GO中,开启15个线程,每个线程把全局变量遍历增加100000次,因此预测结果是 15*100
- 问题描述现有一个有向赋权图。如下图所示:问题:根据每条边的权值,求出从起点s到其他每个顶点的最短路径和最短路径的长度。说明:不考虑权值为负的
- 提示:以下是本篇文章正文内容,下面案例可供参考一、uni-app中自带的弹窗示例:在前端开发中,为了优化用户的交互体验,常需要用到弹窗来进行
- PHP session用法其实很简单它可以把用户提交的数据以全局变量形式保存在一个session中并且会生成一个唯一的session_id,
- 可迭代(iterable)迭代(遍历)就是按照某种顺序逐个访问对象中的每一项。Python中有很多对象都是可以通过for语句来直接遍历的,例
- 官网地址:https://dev.mysql.com/downloads/mysql/我这里是RHEL6.5的系统,因此选择RedHat 6
- 为什么会用到 replace取名是一个很有讲究的事情,但每个人都不一样。一开始,我写了一个 A 项目,代码仓名称为 project-alph
- Python中国象棋单机版鼠标点击操作;两天制作,较为粗糙,很多效果还未实现。# -*- coding: utf-8 -*-"&q
- 缘由最近在做公司的一个点餐H5项目需要前端动态计算用户选的商品的总价(单价*数量)和购物车的总价格时发现关于 JavaScript 浮点数计
- 首字母为英文和下划线,其它部分则可以是英文、数字和下划线(即:_),而变量名称是区分大小写,即变量temp与Temp为不同变量。变量的基本用
- 如果和不同的后台调接口,如果后台接口没有合到一起,前端可以配不同的代理来共同访问他们的接口在config文件夹下的index.js中设置如下
- 最近产品妹子提出了一个体验issue —— 用 iOS 在手Q阅读书友交流区发表书评时,光标点击总是不好定位到正确的位置:如上图,具体表现是
- 很有趣的招聘方式和题目:以下是该次招聘前端开发工程师的聘题解答:小贤是一条可爱的小狗(Dog),它的叫声很好听(wow),每次看到主人的时候
- 一般的网站会有很多页面,面包屑导航可以大大改善用户寻找他们的路径的方法。就可用性而言,面包屑可以减少一个网站的用户返回上一级页面的操作次数,
- 初学python ,研究了几天,写了一个python 调用 有道api接口程序效果看下图:申明:代码仅供和我一样的初学者学习交流有道api申
- 目录一、建立画布二、用plt.subplot函数建立坐标系,并分别绘制折线图和柱状图三、完整代码如下所示四、对应效果图如下所示一、建立画布i
- 前面介绍了python在ubuntu16.04环境下,python的虚拟环境virtualenv的安装,下面介绍在windows环境下的安装
- 本文实例讲述了Python实现对不原生支持比较操作的对象排序算法。分享给大家供大家参考,具体如下:问题:想在同一个类的实例之间做排序,但是它