详解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基础教程之python消息摘要算法使用示例
Feb 10 Python
部署Python的框架下的web app的详细教程
Apr 30 Python
Python的装饰器模式与面向切面编程详解
Jun 21 Python
Python无损音乐搜索引擎实现代码
Feb 02 Python
Python面向对象程序设计多继承和多态用法示例
Apr 08 Python
django框架模板中定义变量(set variable in django template)的方法分析
Jun 24 Python
Python collections中的双向队列deque简单介绍详解
Nov 04 Python
Tensorflow tf.nn.atrous_conv2d如何实现空洞卷积的
Apr 20 Python
Python字典dict常用方法函数实例
Nov 09 Python
基于python模拟bfs和dfs代码实例
Nov 19 Python
python 基于opencv 绘制图像轮廓
Dec 11 Python
python 利用百度API识别图片文字(多线程版)
Dec 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
PHP安装全攻略:APACHE
2006/10/09 PHP
PHP与Java进行通信的实现方法
2013/10/21 PHP
PHP设计模式之观察者模式(Observer)详细介绍和代码实例
2014/04/08 PHP
Thinkphp中import的几个用法详细介绍
2014/07/02 PHP
常见PHP数据库解决方案分析介绍
2015/09/24 PHP
浅谈php处理后端&amp;接口访问超时的解决方法
2016/10/29 PHP
PHP7移除的扩展和SAPI
2021/03/09 PHP
js使用ajax读博客rss示例
2014/05/06 Javascript
jQuery使用empty()方法删除元素及其所有子元素的方法
2015/03/26 Javascript
手机开发必备技巧:javascript及CSS功能代码分享
2015/05/25 Javascript
JS右下角广告窗口代码(可收缩、展开及关闭)
2015/09/04 Javascript
js表单验证实例讲解
2016/03/31 Javascript
jQuery实现下拉加载功能实例代码
2016/04/01 Javascript
js 递归和定时器的实例解析
2017/02/03 Javascript
JavaScript的六种继承方式(推荐)
2017/06/26 Javascript
vue中遇到的坑之变化检测问题(数组相关)
2017/10/13 Javascript
基于vue2实现上拉加载功能
2017/11/28 Javascript
JS排序算法之冒泡排序,选择排序与插入排序实例分析
2017/12/13 Javascript
vue实现分页组件
2020/06/16 Javascript
vue实现搜索功能
2019/05/28 Javascript
JS实现多功能计算器
2020/10/28 Javascript
[01:26]神话结束了,却也刚刚开始——DOTA2新英雄玛尔斯驾临战场
2019/03/10 DOTA
python使用心得之获得github代码库列表
2014/06/25 Python
Python使用logging结合decorator模式实现优化日志输出的方法
2016/04/16 Python
一文总结学习Python的14张思维导图
2017/10/17 Python
Python zip()函数用法实例分析
2018/03/17 Python
python数字图像处理之高级形态学处理
2018/04/27 Python
pygame游戏之旅 添加icon和bgm音效的方法
2018/11/21 Python
python利用ffmpeg进行录制屏幕的方法
2019/01/10 Python
Python基于requests库爬取网站信息
2020/03/02 Python
Guess欧洲官网:美国服饰品牌
2019/08/06 全球购物
会务接待方案
2014/02/27 职场文书
幼师求职自荐信
2014/05/31 职场文书
2015年教师新年寄语
2014/12/08 职场文书
护士个人年终总结
2015/02/13 职场文书
vue响应式原理与双向数据的深入解析
2021/06/04 Vue.js