同步代码(synchrnous code)咱们都很熟悉,就是运行完一个步骤再运行下一个。要在同步代码里面实现"同时"运行多个任务,最简单也是最直观地方式就是运行多个 threads 或者多个 processes。这个层次的『同时运行』多个任务,是操做系统协助完成的。 也就是操做系统的任务调度系统来决定何时运行这个任务,何时切换任务,你本身,做为一个应用层的程序员,是没办法进行干预的。程序员
我相信你也已经据说了什么关于 thread 和 process 的抱怨:process 过重,thread 又要牵涉到不少头条的锁问题。尤为是对于一个 Python 开发者来讲,因为GIL(全局解释器锁)的存在,多线程没法真正使用多核,若是你用多线程来运行计算型任务,速度会更慢。编程
异步编程与之不一样的是,值使用一个进程,不使用 threads,可是也能实现"同时"运行多个任务(这里的任务其实就是函数)。flask
这些函数有一个很是 nice 的 feature:必要的能够暂停,把运行的权利交给其余函数。等到时机恰当,又能够恢复以前的状态继续运行。这听上去是否是有点像进程呢?能够暂停,能够恢复运行。只不过进程的调度是操做系统完成的,这些函数的调度是进程本身(或者说程序员你本身)完成的。这也就意味着这将省去了不少计算机的资源,由于进程的调度必然须要大量 syscall,而 syscall 是很昂贵的。服务器
import asyncio async def execute(x): print('Number:', x) return x coroutine = execute(1) print('Coroutine:', coroutine) print('After calling execute') loop = asyncio.get_event_loop() task = loop.create_task(coroutine) print('Task:', task) loop.run_until_complete(task) print('Task:', task) print('After calling loop') # print('Task Result:', task.result()) 这样也能查看task执行的结果
运行结果:网络
Coroutine: <coroutine object execute at 0x10e0f7830> After calling execute Task: <Task pending coro=<execute() running at demo.py:4>> Number: 1 Task: <Task finished coro=<execute() done, defined at demo.py:4> result=1> After calling loop
咱们使用 async 定义了一个 execute() 方法,方法接收一个数字参数,方法执行以后会打印这个数字。
随后咱们直接调用了这个方法,然而这个方法并无执行,而是返回了一个 coroutine 协程对象。session
随后咱们使用 get_event_loop() 方法建立了一个事件循环 loop,并调用了 loop 对象的 run_until_complete() 方法将协程注册到事件循环 loop 中,而后启动。最后咱们才看到了 execute() 方法打印了输出结果。
可见,async 定义的方法就会变成一个没法直接执行的 coroutine 对象,必须将其注册到事件循环中才能够执行。
多线程
咱们也能够不使用task来运行,它里面相比 coroutine 对象多了运行状态,好比 running、finished 等,咱们能够用这些状态来获取协程对象的执行状况。app
将 coroutine 对象传递给 run_until_complete() 方法的时候,实际上它进行了一个操做就是将 coroutine 封装成了 task 对象,如:异步
import asyncio async def execute(x): print('Number:', x) coroutine = execute(1) print('Coroutine:', coroutine) print('After calling execute') loop = asyncio.get_event_loop() loop.run_until_complete(coroutine) print('After calling loop')
查看了源码,正好能够验证上面这一观点:async
run_until_complete()这个方法位于源码中的base_events.py,函数有句注释:
Run until the Future is done.If the argument is a coroutine, it is wrapped in a Task.
咱们用一个网络请求做为示例,这就是一个耗时等待的操做,由于咱们请求网页以后须要等待页面响应并返回结果。耗时等待的操做通常都是 IO 操做,好比文件读取、网络请求等等。协程对于处理这种操做是有很大优点的,当遇到须要等待的状况的时候,程序能够暂时挂起,转而去执行其余的操做,从而避免一直等待一个程序而耗费过多的时间,充分利用资源。为了测试,我本身先经过flask 建立一个实验环境:
from flask import Flask import time app = Flask(__name__) @app.route('/') def index(): time.sleep(3) return 'Hello!' if __name__ == '__main__': app.run(threaded=True)
开始测试...
import asyncio import aiohttp import time start = time.time() 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' # 访问flask搭建的服务器(睡眠3秒),模仿IO阻塞 print('Waiting for', url) result = await get(url) print('Get response from', url, 'Result:', result) tasks = [asyncio.ensure_future(request()) for _ in range(5)] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) end = time.time() print('Cost time:', end - start)
运行结果:
Waiting for http://127.0.0.1:5000 Waiting for http://127.0.0.1:5000 Waiting for http://127.0.0.1:5000 Waiting for http://127.0.0.1:5000 Waiting for http://127.0.0.1:5000 Get response from http://127.0.0.1:5000 Result: Hello! Get response from http://127.0.0.1:5000 Result: Hello! Get response from http://127.0.0.1:5000 Result: Hello! Get response from http://127.0.0.1:5000 Result: Hello! Get response from http://127.0.0.1:5000 Result: Hello! Cost time: 3.0199508666992188
咱们发现此次请求的耗时由 15 秒变成了 3 秒,耗时直接变成了原来的 1/5。
代码里面咱们使用了 await,后面跟了 get() 方法,在执行这五个协程的时候,若是遇到了 await,那么就会将当前协程挂起,转而去执行其余的协程,直到其余的协程也挂起或执行完毕,再进行下一个协程的执行。
协程"同时"运行多个任务的基础是函数能够暂停(await实际就是用到了yield)。上面的代码中使用到了 asyncio的 event_loop,它作的事情,本质上来讲就是当函数暂停时,切换到下一个任务,当时机恰当(这个例子中是请求完成了)恢复函数让他继续运行(这有点像操做系统了)。