异步IO协程 (asyncio) ——Python

同步与异步

    同步:先执行第一个事务,如果遇到阻塞(time.sleep()),会一直等待,直到第一个事务执行完毕,才会执行第二个事务

    异步:与同步是相对的,指执行第一个事务的时候,如果遇到阻塞,会直接执行第二个事务,不会等待。通过状态、通知、回调来调用处理结果

异步

      进程、线程都可以实现异步,用协程也可以实现异步

      使用异步 IO,无非是提高我们写的软件系统的并发。 这个软件系统, 可以是网络爬虫,也可以是 Web 服务等等。

      并发的方式有多种, 多线程, 多进程, 异步 IO 等

      多线程和多进程更多应用于 CPU 密集型的场景, 比如科学计算的时间都耗费在 CPU 上, 利用多核 CPU 来分担计算任务。

      多线程和多进程之间的场景切换和通讯代价很高, 不适合 IO 密集型的场景。

      而异步 IO 就是非常适合 IO 密集型的场景比如网络爬虫和 Web 服务

      IO 就是读写磁盘、 读写网络的操作, 这种读写速度比读写内存、 CPU 缓存慢得多, 前者的耗时是后者的成千上万倍甚至更多。 这就导致, IO 密集型的场景 99%以上的时间都花费在 IO 等待的时间上异步 IO 就是把 CPU 从漫长的等待中解放出来的方法

asyncio

      asyncio 是 Python 3.4 版本引入的标准库, 直接内置了对异步 IO 的支持。

      asyncio 的编程模型就是一个消息循环。 我们从 asyncio 模块中直接获取一个 EventLoop 的引用, 然后把需要执行的协程扔到 EventLoop 中执行, 就实现了异步 IO

(1)event_loop 事件循环: 程序开启一个无限的循环, 程序员会把一些函数注册到事件循环上。 当满足事件发生的时候, 调用相应的协程函数。

(2) coroutine 协程: 协程对象, 指一个使用 async 关键字定义的函数, 它的调用不会立即执行函数, 而是会返回一个协程对象。 协程对象需要注册到事件循环, 由事件循环调用。

(3) task 任务: 一个协程对象就是一个原生可以挂起的函数, 任务则是对协程进一步
封装, 其中包含任务的各种状态。

(4) future: 代表将来执行或没有执行的任务的结果。 它和 task 上没有本质的区别
(5) async/await 关键字: python3.5 用于定义协程的关键字, async 定义一个协程, await
用于挂起阻塞的异步调用接口。

 

同步

import time
now = lambda:time.time()
def foo():
	time.sleep(1)

start = now()
for i in range(5):
	foo()
print('同步所花费的时间: ',now()-start)

异步

import asyncio
import time

now = lambda:time.time()
print('协程实现异步')
async def foo():
	asyncio.sleep(1)
start = now()
loop = asyncio.get_event_loop()
for i in range(5):
	loop.run_until_complete(foo())
print('异步所花费的时间: ',now()-start)

协程异步几乎是同时进行执行的

时间差距相当大

定义协程

定义一个协程很简单, 使用 async 关键字, 就像定义普通函数一样

使用async来修饰一个函数,则该函数就称为一个协程对象

import asyncio

#通过aysnc定义一个协程,该协程不能直接运行,需要将协程加入到事件循环中
async def do_work(x):
	print('waiting: %d'%x)

#调用协程
#1、创建一个事件循环
loop = asyncio.get_event_loop()
#2、将协程对象加入到事件循环中
loop.run_until_complete(do_work(3))

创建任务task

      协程对象不能直接运行, 在注册事件循环的时候, 其实是 run_until_complete 方法将协程包装成为了一个任务(task) 对象。 所谓 task 对象是 future 类的子类。 保存了协程运行后的状态, 用于未来获取协程的结果。

      asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个 task,run_until_complete 的参数是一个 futrue 对象。 当传入一个协程, 其内部会自动封装成 task,task 是 future 的子类。 isinstance(task, asyncio.Future)将会输出 True。

      创建 task 后, task 在加入事件循环之前是 pending 状态, 因为 do_work 中没有耗时的阻
塞操作, task 很快就执行完毕了。 后面打印的 finished 状态。
 

写法1

import asyncio
import time

now = lambda:time.time()
async def do_work(x):
	print('Waiting: ',x)

#获取协程对象
coroutine = do_work(2)
#创建事件循环对象
loop = asyncio.get_event_loop()
#创建一个task
task = asyncio.ensure_future(coroutine)
print(task)
#将协程对象注册到事件循环中
loop.run_until_complete(task)
print(task)

写法2

import asyncio
import time

now = lambda:time.time()
async def do_work(x):
	print('Waiting: ',x)

#获取协程对象
coroutine = do_work(2)
#创建事件循环对象
loop = asyncio.get_event_loop()
#创建一个task
task = loop.create_task(coroutine)
print(task)
#将协程对象注册到事件循环中
loop.run_until_complete(task)
print(task)

绑定回调

        协程对象函数是没有返回的,如果我们加一个return

        绑定回调, 在 task 执行完毕的时候可以获取执行的结果, 回调的最后一个参数是 future对象, 通过该对象可以获取协程返回值。 如果回调需要多个参数, 可以通过偏函数导入

        coroutine 执行结束时候会调用回调函数。 并通过参数 future 获取协程执行的结果。 创建的 task 和回调里的 future 对象, 实际上是同一个对象

import time
import asyncio

now = lambda: time.time()
async def do_some_work(x):
	print('Waiting: ', x)
	return 'Done after {}s'.format(x)
def callback(future):
	print('Callback: ', future.result())

coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
task.add_done_callback(callback)
loop.run_until_complete(task)

也可以不用回调函数,直接读取 task 的 result 方法

import asyncio


async def do_some_work(x):
	print('Waiting {}'.format(x))
	return 'Done after {}s'.format(x)

coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
print('Task ret: {}'.format(task.result()))

阻塞和 await

       使用 async 可以定义协程对象, 使用 await 可以针对耗时的操作进行挂起, 就像生成器里的 yield 一样, 函数让出控制权。 协程遇到 await, 事件循环将会挂起该协程, 执行别的协程, 直到其他的协程也挂起或者执行完毕, 再进行下一个协程的执行。
      耗时的操作一般是一些 IO 操作, 例如网络请求, 文件读取等。 我们使用 asyncio.sleep函数来模拟 IO 操作。 协程的目的也是让这些 IO 操作异步化。
 

import asyncio
import time

now = lambda: time.time()

async def do_some_work(x):
	print('Waiting: ', x)
	await asyncio.sleep(x)
	return 'Done after {}s'.format(x)

start = now()
#创建协程对象
coroutine = do_some_work(2)
#协程不能直接运行,要创建事件循环
loop = asyncio.get_event_loop()
#创建任务
task = asyncio.ensure_future(coroutine)
#执行任务
loop.run_until_complete(task)
print('Task ret: ', task.result())
print('TIME: ', now() - start)

asyncio实现并发

       asyncio 实现并发, 就需要多个协程来完成任务, 每当有任务阻塞的时候就 await, 然后其他协程继续工作。 创建多个协程的列表, 然后将这些协程注册到事件循环中。

import asyncio
import time

now = lambda: time.time()
async def do_some_work(x):
	print('Waiting: ', x)
	await asyncio.sleep(x)
	return 'Done after {}s'.format(x)

start = now()
#创建多个协程对象
#第一个耗时1s,第二个耗时2s,第三个耗时4s
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)

#创建任务列表
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]

#将任务列表注册到事件循环中
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

#获取返回的结果
for task in tasks:
	print('Task ret: ', task.result())
	
print('TIME: ', now() - start)


     总时间为 4s 左右。 4s 的阻塞时间, 足够前面两个协程执行完毕。 如果是同步顺序的任务, 那么至少需要 7s。 此时我们使用了 aysncio 实现了并发。 asyncio.wait(tasks) 也可以使用asyncio.gather(*tasks) ,前者接受一个 task 列表, 后者接收一堆 task

发布了487 篇原创文章 · 获赞 197 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/hxxjxw/article/details/105522487