详解python异步编程之asyncio(百万并发)


Posted in Python onJuly 07, 2018

前言:python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病。然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率,弥补了python性能方面的短板,如最新的微服务框架japronto,resquests per second可达百万级。

python还有一个优势是库(第三方库)极为丰富,运用十分方便。asyncio是python3.4版本引入到标准库,python2x没有加这个库,毕竟python3x才是未来啊,哈哈!python3.5又加入了async/await特性。

在学习asyncio之前,我们先来理清楚同步/异步的概念:

同步是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行。。。

异步是和同步相对的,异步是指在处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、通知、回调来通知调用者处理结果。

一、asyncio

下面通过举例来对比同步代码和异步代码编写方面的差异,其次看下两者性能上的差距,我们使用sleep(1)模拟耗时1秒的io操作。

同步代码:

import time

def hello():
  time.sleep(1)

def run():
  for i in range(5):
    hello()
    print('Hello World:%s' % time.time()) # 任何伟大的代码都是从Hello World 开始的!
if __name__ == '__main__':
  run()

输出:(间隔差不多是1s)

Hello World:1527595175.4728756
Hello World:1527595176.473001
Hello World:1527595177.473494
Hello World:1527595178.4739306
Hello World:1527595179.474482 

异步代码:

import time
import asyncio

# 定义异步函数
async def hello():
  asyncio.sleep(1)
  print('Hello World:%s' % time.time())

def run():
  for i in range(5):
    loop.run_until_complete(hello())

loop = asyncio.get_event_loop()
if __name__ =='__main__':
  run()

输出:

Hello World:1527595104.8338501
Hello World:1527595104.8338501
Hello World:1527595104.8338501
Hello World:1527595104.8338501
Hello World:1527595104.8338501 

async def 用来定义异步函数,其内部有异步操作。每个线程有一个事件循环,主线程调用asyncio.get_event_loop()时会创建事件循环,你需要把异步的任务丢给这个循环的run_until_complete()方法,事件循环会安排协同程序的执行。  

二、aiohttp

如果需要并发http请求怎么办呢,通常是用requests,但requests是同步的库,如果想异步的话需要引入aiohttp。这里引入一个类,from aiohttp import ClientSession,首先要建立一个session对象,然后用session对象去打开网页。session可以进行多项操作,比如post, get, put, head等。

基本用法:

async with ClientSession() as session:
async with session.get(url) as response:

aiohttp异步实现的例子:

import asyncio
from aiohttp import ClientSession


tasks = []
url = "https://www.baidu.com/{}"
async def hello(url):
  async with ClientSession() as session:
    async with session.get(url) as response:
      response = await response.read()
      print(response)

if __name__ == '__main__':
  loop = asyncio.get_event_loop()
  loop.run_until_complete(hello(url))

首先async def 关键字定义了这是个异步函数,await 关键字加在需要等待的操作前面,response.read()等待request响应,是个耗IO操作。然后使用ClientSession类发起http请求。

多链接异步访问

如果我们需要请求多个URL该怎么办呢,同步的做法访问多个URL只需要加个for循环就可以了。但异步的实现方式并没那么容易,在之前的基础上需要将hello()包装在asyncio的Future对象中,然后将Future对象列表作为任务传递给事件循环。

import time
import asyncio
from aiohttp import ClientSession

tasks = []
url = "https://www.baidu.com/{}"
async def hello(url):
  async with ClientSession() as session:
    async with session.get(url) as response:
      response = await response.read()
#      print(response)
      print('Hello World:%s' % time.time())

def run():
  for i in range(5):
    task = asyncio.ensure_future(hello(url.format(i)))
    tasks.append(task)


if __name__ == '__main__':
  loop = asyncio.get_event_loop()
  run()
  loop.run_until_complete(asyncio.wait(tasks))

输出:

Hello World:1527754874.8915546
Hello World:1527754874.899039
Hello World:1527754874.90004
Hello World:1527754874.9095392
Hello World:1527754874.9190395 

收集http响应

好了,上面介绍了访问不同链接的异步实现方式,但是我们只是发出了请求,如果要把响应一一收集到一个列表中,最后保存到本地或者打印出来要怎么实现呢,可通过asyncio.gather(*tasks)将响应全部收集起来,具体通过下面实例来演示。

import time
import asyncio
from aiohttp import ClientSession

tasks = []
url = "https://www.baidu.com/{}"
async def hello(url):
  async with ClientSession() as session:
    async with session.get(url) as response:
#      print(response)
      print('Hello World:%s' % time.time())
      return await response.read()

def run():
  for i in range(5):
    task = asyncio.ensure_future(hello(url.format(i)))
    tasks.append(task)
  result = loop.run_until_complete(asyncio.gather(*tasks))
  print(result)

if __name__ == '__main__':
  loop = asyncio.get_event_loop()
  run()

输出:

Hello World:1527765369.0785167
Hello World:1527765369.0845182
Hello World:1527765369.0910277
Hello World:1527765369.0920424
Hello World:1527765369.097017
[b'<!DOCTYPE html>\r\n<!--STATUS OK-->\r\n<html>\r\n<head>\r\n......

异常解决

假如你的并发达到1000个,程序会报错:ValueError: too many file descriptors in select()。这个报错的原因是因为 Python 调取的 select 对打开的文件字符有最大长度限制。这里我们有两种方法解决这个问题:1.我们可以需要限制并发数量。一次不要塞那么多任务,或者限制最大并发数量。2.我们可以使用回调的方式。这里个人推荐限制并发数的方法,设置并发数为500或者600,处理速度更快。

#coding:utf-8
import time,asyncio,aiohttp


url = 'https://www.baidu.com/'
async def hello(url,semaphore):
  async with semaphore:
    async with aiohttp.ClientSession() as session:
      async with session.get(url) as response:
        return await response.read()


async def run():
  semaphore = asyncio.Semaphore(500) # 限制并发量为500
  to_get = [hello(url.format(),semaphore) for _ in range(1000)] #总共1000任务
  await asyncio.wait(to_get)


if __name__ == '__main__':
#  now=lambda :time.time()
  loop = asyncio.get_event_loop()
  loop.run_until_complete(run())
  loop.close()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python bottle框架支持jquery ajax的RESTful风格的PUT和DELETE方法
May 24 Python
Python基础语言学习笔记总结(精华)
Nov 14 Python
python实现windows下文件备份脚本
May 27 Python
python matlibplot绘制3D图形
Jul 02 Python
用Python和WordCloud绘制词云的实现方法(内附让字体清晰的秘笈)
Jan 08 Python
python 用for循环实现1~n求和的实例
Feb 01 Python
django foreignkey外键使用的例子 相当于left join
Aug 06 Python
Python Django 添加首页尾页上一页下一页代码实例
Aug 21 Python
python numpy库linspace相同间隔采样的实现
Feb 25 Python
selenium携带cookies模拟登陆CSDN的实现
Jan 19 Python
仅用几行Python代码就能复制她的U盘文件?
Jun 26 Python
python中 .npy文件的读写操作实例
Apr 14 Python
基于Python开发chrome插件的方法分析
Jul 07 #Python
Python实现基于C/S架构的聊天室功能详解
Jul 07 #Python
Python实现的txt文件去重功能示例
Jul 07 #Python
Django 多语言教程的实现(i18n)
Jul 07 #Python
python利用requests库进行接口测试的方法详解
Jul 06 #Python
python生成密码字典的方法
Jul 06 #Python
Python 3.x 判断 dict 是否包含某键值的实例讲解
Jul 06 #Python
You might like
通过html表格发电子邮件
2006/10/09 PHP
ExtJS与PHP、MySQL实现存储的方法
2010/04/02 PHP
php空间不支持socket但支持curl时recaptcha的用法
2011/11/07 PHP
php获取$_POST同名参数数组的实现介绍
2013/06/30 PHP
php加密算法之实现可逆加密算法和解密分享
2014/01/21 PHP
PHP图片处理之使用imagecopyresampled函数裁剪图片例子
2014/11/19 PHP
PHP中时间加减函数strtotime用法分析
2017/04/26 PHP
JavaScript页面刷新与弹出窗口问题的解决方法
2010/03/02 Javascript
jQuery EasyUI API 中文文档 - EasyLoader 加载器
2011/09/29 Javascript
《JavaScript高级程序设计》阅读笔记(三) ECMAScript中的引用类型
2012/02/27 Javascript
PHP使用方法重载实现动态创建属性的get和set方法
2014/11/17 Javascript
微信小程序  简单实例(阅读器)的实例开发
2016/09/29 Javascript
jQuery表单元素选择器代码实例
2017/02/06 Javascript
Angular4 中内置指令的基本用法
2017/07/31 Javascript
Vue中组件之间数据的传递的示例代码
2017/09/08 Javascript
vue组件父与子通信详解(一)
2017/11/07 Javascript
详解vue-cli3使用
2018/08/14 Javascript
vue中render函数的使用详解
2018/10/12 Javascript
Vuex的基本概念、项目搭建以及入坑点
2018/11/04 Javascript
echarts实现折线图的拖拽效果
2019/12/19 Javascript
Node.js API详解之 module模块用法实例分析
2020/05/13 Javascript
JS跨浏览器解析XML应用过程详解
2020/10/16 Javascript
[42:00]完美世界DOTA2联赛PWL S3 Phoenix vs INK ICE 第一场 12.13
2020/12/17 DOTA
[01:03:41]DOTA2-DPC中国联赛 正赛 Dynasty vs XG BO3 第三场 2月2日
2021/03/11 DOTA
Django model序列化为json的方法示例
2018/10/16 Python
python如何停止递归
2020/09/09 Python
浅析图片上传及canvas压缩的流程
2020/06/10 HTML / CSS
俄罗斯游戏商店:Buka
2020/03/01 全球购物
旅游管理实习自我鉴定
2013/09/29 职场文书
女娲补天教学反思
2014/02/05 职场文书
公证委托书模板
2014/04/03 职场文书
党的群众路线教育实践活动个人整改措施
2014/10/27 职场文书
2015年教师党员承诺书
2015/04/27 职场文书
浅谈如何写好演讲稿?
2019/06/12 职场文书
导游词之天津盘山
2019/11/01 职场文书
MySQL中日期型单行函数代码详解
2021/06/21 MySQL