python协程异步 I/O----asyncio

python的线程是由操作系统调度,由于GIL锁的存在,python操作线程时只能操作单个线程,当单个线程遇到IO阻塞或执行时间较长,则会被迫交出cpu的控制权,转交给其他线程处理,协程是存在单线程里的,是由应用程序控制的,跟线程一样,遇到IO阻塞或执行时间较长,会被迫交出程序的控制权,切换到其他协程,切换的开销比线程的切换开销小,这种机制能够提高效率。

协程本质上是单线程,协程的调度是在单个线程里执行的,切换的开销比较小,因此效率上略微比多线程高。有了协程,在执行IO耗时操作时,函数可以临时让出控制权,让CPU去执行其他函数。

协程的应用场景:IO密集型。

python3.4中使用协程的库:asyncio

1. 协程的简单实例

import asyncio
import time


async def task():
    print("task start")
    time.sleep(2)
    print("task end")


coroutine = task()  # 返回coroutine对象,方法没有执行
print("创建coroutine对象,方法task没有执行")
loop = asyncio.get_event_loop() # 获取事件循环loop对象
start = time.time()
loop.run_until_complete(coroutine)  # 将协程对象注册到事件循环loop中并启动
print(f"调用协程任务,耗时{time.time() - start} 秒")

输出:

创建coroutine对象,方法task没有执行
task start
task end
耗时2.0004966259002686 秒

说明:在方法task()前面使用关键字async,声明该方法是一个协程,然后直接调用该方法,但是该方法并没有立即执行,而是返回了一个coroutine对象。使用get_event_loop()方法获取一个事件循环loop对象,并调用loop对象的run_util_complete()方法把协程对象注册到事件循环中,并启动task方法。async定义的方法无法直接执行,必须将其注册到事件循环中才可以执行。

2. 给协程方法绑定回调函数

import asyncio
import time


async def _task():
    print("task start")
    time.sleep(2)
    print("task end")
    return "_task方法返回的结果"


def callback(task):
    """
    :param task: 协程任务对象
    :return: 
    """
    print("回调函数开始运行")
    print(f"状态:{task.result()}")


coroutine = _task()
print("创建coroutine对象,方法task没有执行")
task = asyncio.ensure_future(coroutine)  # 返回task对象
task.add_done_callback(callback)  # 添加回调函数
loop = asyncio.get_event_loop()
print("开始调用协程任务")
loop.run_until_complete(task)
print("结束调用协程任务")

输出:

创建coroutine对象,方法task没有执行
开始调用协程任务
task start
task end
回调函数开始运行
状态:_task方法返回的结果
结束调用协程任务

3. 协程的并发

以上两个实例只执行了一个任务,现在演示下执行多个IO密集型任务。

需求说明:定义一个task耗时任务列表,使用协程并发执行列表中的任务。await关键字可以挂起一个函数或方法。

import asyncio
import time

async def task():
    print("task start")
    # 异步调用asyncio.sleep(1)等待
    await  asyncio.sleep(2)
    print("task end" )

# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
tasks = [task() for _ in range(5)]
start = time.time()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
end = time.time()
print(f"用时 {end-start} 秒")

输出:

task start
task start
task start
task start
task start
task end
task end
task end
task end
task end
用时 2.000945806503296 秒

说明:首先定义个耗时2秒的函数,这里我们使用了await关键字,根据官方文档说明,await关键字后面的对象必须是如下类型之一。

  • 一个原生的coroutine对象。
  • 一个由type.coroutine()修饰的生成器,这个生成器可以返回coroutine对象。
  • 一个包含await方法的对象返回的迭代器

asynicio.sleep(2)是一个由coroutine修饰的生成器对象,表示等待2秒,是异步的,接下来我们定义了一个task列表,由5个task()组成,输出的耗时接近于单个任务的耗时,最后使用loop.run_until_complete(asyncio.wait(tasks))提交执行,从而实现并发操作。

4.协程的异步请求

IO密集型任务常见的应用有网络请求、文件读取等,模拟异步请求之前,我们本地起了一个flask的web服务器。

flask的安装:pip3 install flask

4.1 启动一个简单的Flask web服务器。

from flask import Flask
import time

app = Flask(__name__)

@app.route('/')
def index():
    time.sleep(3)
    return 'Hello World!'

if __name__ == '__main__':
    app.run(threaded=True)

输出:

 * Serving Flask app "coroutine_flask_demo" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

说明: 在浏览器中输入:http://127.0.0.1:5000/ ,3秒后页面会显示Hello World!。注意:服务器启动时,run()方法中设置参数threaded=True,表示Flask启动了多线程、多进程。否则的话默认是只有一个线程的,如果设置此参数开启多线程,那么同一时刻遇到对个请求时,也只能一个个排队等待,瓶颈就会出现在服务端。所以简单的flask服务设置threaded=True是很有必要的,如果复杂的项目,推荐使用nginx+uwsgi来处理请求并发。

4.2 协程异步请求的实现

注意:使用aiohttp来发起异步请求,我们常用的request是同步的。

以下代码通过aiohttp的ClientSession类的get()方法进行100次并发请求

import asyncio
import aiohttp
import time

now = lambda: time.strftime("%H:%M:%S")


async def get(url):
    session = aiohttp.ClientSession()
    response = await session.get(url)  # 挂起请求
    result = await response.text()  # 获取响应内容
    session.close()
    return result


async def request():
    url = "http://127.0.0.1:5000"
    print(f"{now()} 请求 {url}")
    result = await get(url)
    print(f"{now()} 得到响应 {result}")


start = time.time()
tasks = [asyncio.ensure_future(request()) for _ in range(100)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

print(f"耗时 { time.time() - start } 秒")

输出:

耗时 3.0944957733154297 秒

说明:通过输出可以看到使用协程进行100次异步请求的耗时接近一次的请求的耗时。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大帅不是我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值