Python从使用线程到使用async/await的深入讲解


Posted in Python onSeptember 16, 2018

前言

为了简化并更好地标识异步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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python进程间通信用法实例
Jun 04 Python
Python中asyncore异步模块的用法及实现httpclient的实例
Jun 28 Python
python实现自主查询实时天气
Jun 22 Python
Python查找数组中数值和下标相等的元素示例【二分查找】
Feb 13 Python
python实现移位加密和解密
Mar 22 Python
Python GUI编程完整示例
Apr 04 Python
matlab灰度图像调整及imadjust函数的用法详解
Feb 27 Python
执行Python程序时模块报错问题
Mar 26 Python
使用Python3 poplib模块删除服务器多天前的邮件实现代码
Apr 24 Python
python实现将中文日期转换为数字日期
Jul 14 Python
python飞机大战游戏实例讲解
Dec 04 Python
pytorch MSELoss计算平均的实现方法
May 12 Python
推荐10款最受Python开发者欢迎的Python IDE
Sep 16 #Python
python3中os.path模块下常用的用法总结【推荐】
Sep 16 #Python
python os.path模块常用方法实例详解
Sep 16 #Python
python中的不可变数据类型与可变数据类型详解
Sep 16 #Python
Python连接Mssql基础教程之Python库pymssql
Sep 16 #Python
python将秒数转化为时间格式的实例
Sep 16 #Python
Python日期时间模块datetime详解与Python 日期时间的比较,计算实例代码
Sep 14 #Python
You might like
php 提速工具eAccelerator 配置参数详解
2010/05/16 PHP
PHP对字符串的递增运算分析
2010/08/08 PHP
zf框架的数据库追踪器使用示例
2014/03/13 PHP
laravel异步监控定时调度器实例详解
2019/06/21 PHP
BOOM vs RR BO3 第一场2.13
2021/03/10 DOTA
javascript实现 在光标处插入指定内容
2007/05/25 Javascript
将函数的实际参数转换成数组的方法
2010/01/25 Javascript
获取中文字符串的实际长度代码
2014/06/05 Javascript
JQuery中使用.each()遍历元素学习笔记
2014/11/08 Javascript
JavaScript事件委托技术实例分析
2015/02/06 Javascript
微信小程序 WXML、WXSS 和JS介绍及详解
2016/10/08 Javascript
jQuery与JavaScript节点创建方法的对比
2016/11/18 Javascript
遍历json获得数据的几种方法小结
2017/01/21 Javascript
JavaScript实现选中文字提示新浪微博分享效果
2017/06/15 Javascript
深入理解AngularJs-scope的脏检查(一)
2017/06/19 Javascript
Vue.js上下滚动加载组件的实例代码
2017/07/17 Javascript
你应该知道的几类npm依赖包管理详解
2017/10/06 Javascript
JS写XSS cookie stealer来窃取密码的步骤详解
2017/11/20 Javascript
JS实现的邮箱提示补全效果示例
2018/01/30 Javascript
js的对象与函数详解
2019/01/21 Javascript
[04:20]DOTA2-DPC中国联赛 正赛 VG vs LBZS 选手采访 1月19日
2021/03/11 DOTA
初步认识Python中的列表与位运算符
2015/10/12 Python
pytorch 转换矩阵的维数位置方法
2018/12/08 Python
Ranorex通过Python将报告发送到邮箱的方法
2020/01/12 Python
浅谈Python3实现两个矩形的交并比(IoU)
2020/01/18 Python
Python模块 _winreg操作注册表
2020/02/05 Python
浅谈Python里面None True False之间的区别
2020/07/09 Python
Python根据字典的值查询出对应的键的方法
2020/09/30 Python
Python命令行参数定义及需要注意的地方
2020/11/30 Python
基于Python-turtle库绘制路飞的草帽骷髅旗、美国队长的盾牌、高达的源码
2021/02/18 Python
安全责任书范文
2014/03/12 职场文书
领导班子个人对照检查剖析材料
2014/09/29 职场文书
学前班学生评语
2014/12/29 职场文书
2016年党员岗位承诺书
2016/03/24 职场文书
幼儿园2016圣诞节活动总结
2016/03/31 职场文书
SpringCloud Alibaba 基本开发框架搭建过程
2021/06/13 Java/Android