内容简介:注:本文说的同时是一个直观上感觉的概念,只是为了简化,不是严格意义上的同一时刻。同步代码(synchrnous code)我们都很熟悉,就是运行完一个步骤再运行下一个。要在同步代码里面实现"同时"运行多个任务,最简单也是最直观地方式就是运行多个 threads 或者多个 processes。我相信你也已经听说了什么关于 thread 和 process 的抱怨:process 太重,thread 又要牵涉到很多头条的锁问题。尤其是对于一个 Python 开发者来说,由于GIL(全局解释器锁)的存在,
注:本文说的同时是一个直观上感觉的概念,只是为了简化,不是严格意义上的同一时刻。
同步代码(synchrnous code)我们都很熟悉,就是运行完一个步骤再运行下一个。要在同步代码里面实现"同时"运行多个任务,最简单也是最直观地方式就是运行多个 threads 或者多个 processes。 这个层次的『同时运行』多个任务,是操作系统协助完成的。 也就是操作系统的任务调度系统来决定什么时候运行这个任务,什么时候切换任务,你自己,作为一个应用层的程序员,是没办法进行干预的。
我相信你也已经听说了什么关于 thread 和 process 的抱怨:process 太重,thread 又要牵涉到很多头条的锁问题。尤其是对于一个 Python 开发者来说,由于GIL(全局解释器锁)的存在, 多线程无法真正使用多核,如果你用多线程来运行计算型任务,速度会更慢。
异步编程与之不同的是,值使用一个进程,不使用 threads,但是也能实现"同时"运行多个任务(这里的任务其实就是函数)。
这些函数有一个非常 nice 的 feature:必要的可以暂停,把运行的权利交给其他函数。等到时机恰当,又可以恢复之前的状态继续运行。这听上去是不是有点像进程呢?可以暂停,可以恢复运行。只不过进程的调度是操作系统完成的,这些函数的调度是进程自己(或者说 程序员 你自己)完成的。这也就意味着这将省去了很多计算机的资源,因为进程的调度必然需要大量 syscall,而 syscall 是很昂贵的。
异步编程注意事项
有一点是需要格外注意的, 异步代码里面不能用任何会 block 的函数 !也就是说你的代码里面不应该出现下面这些:
- time.sleep()
- 会阻塞的 socket
- requests.get()
- 会阻塞的数据库调用
为什么呢? 在用 thread 或 process 的时候,代码阻塞了有操作系统来帮你调度,所以才不会出现『一处阻塞,处处傻等』的情况。
但是现在,对于操作系统来说,你的进程就是一个普通的进程,他并不知道你分了哪些不同的任务,一切都要靠你自己了。如果你的代码里出现了阻塞的调用,那么其他部分确实就是傻傻地等着。(等下判断一下这会不会出错)。
小试Python asyncio
Python 版本支持情况
- asyncio 模块在 Python3.4 时发布。
- async 和 await 关键字最早在 Python3.5中引入。
- Python3.3之前不支持。
开始动手敲代码
同步版本
就是一个简单的访问百度首页100次,然后打印状态码。
import time import requests def visit_sync(): start = time.time() for _ in range(100): r = requests.get(URL) print(r.status_code) end = time.time() print("visit_sync tasks %.2f seconds" % (end - start)) if __name__ == '__main__': visit_sync() 复制代码
运行一下,发现使用了6.64秒。
异步版本
import time import asyncio import aiohttp async def fetch_async(url): async with aiohttp.ClientSession() as session: async with session.get(url) as resp: status_code = resp.status print(status_code) async def visit_async(): start = time.time() tasks = [] for _ in range(100): tasks.append(fetch_async(URL)) await asyncio.gather(*tasks) end = time.time() print("visit_async tasks %.2f seconds" % (end - start)) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(visit_async()) 复制代码
有几点说明一下:
- 网络访问的部分变了,前面用的是 requests.get(),这里用的是 aiohttp(不是标准库需要自己安装)。
- 调用函数的方式变了,前面通过
visit_sync()
就可以直接运行,异步代码中不能直接visit_async()
,这会提示你一个 warning:
如果打印一下 visit_async()
返回值的类型可以看到,这是一个coroutine(协程)。
正常的姿势是调用 await visit_async()
,就想代码中 await asyncio.gather(*tasks)
一样。但是比较麻烦的一点是 await
只有在以关键字 async
定义的函数里面使用,而我们的 if __name__ == "__main__"
里面没有函数,所以可以把这个 coroutine
传给一个 eventloop。
loop = asyncio.get_event_loop() loop.run_until_complete(visit_async()) 复制代码
运行之后发现,耗时0.34秒,效率提升20多倍。(关于如何有逼格地分析异步效率,可以参考前面写过的一篇文章。)
总结一下
事实上,这篇文章已经引出了异步编程中一个重要的概念:协程。『异步编程101』系列文章后面还会花很多篇幅说一说一下协程。
协程"同时"运行多个任务的基础是 函数可以暂停 (后面我们会讲到这一点是如何实现的,Python 中是通过 yield)。上面的代码中使用到了 asyncio
的 event_loop,它做的事情,本质上来说就是当函数暂停时,切换到下一个任务,当时机恰当(这个例子中是请求完成了)恢复函数让他继续运行(这有点像操作系统了)。
这相比使用多线程或多进程,把调度地任务交给操作系统,在性能上有极大的优势,因为不需要大量的 syscall。同时又解决了多线程数据共享带来的锁的问题。 而且作为一个应用程序开发者,你应该是要比操作系统更懂,哪些时候进行任务切换。
我个人觉得,新时代的程序员,有两点技能是非常重要的: 异步编程的能力和利用多核系统的能力。
觉得不错点个 star?
我的公众号:全栈不存在的
以上所述就是小编给大家介绍的《异步编程 101: 是什么、小试Python asyncio》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入理解Nginx(第2版)
陶辉 / 机械工业出版社 / 2016-2 / 99.00元
本书致力于说明开发Nginx模块的必备知识,第1版发行以后,深受广大读者的喜爱.然而由于Ng,nx功能繁多且性能强大,以致必须了解的基本技能也很庞杂,而第1版成书匆忙,缺失了几个进阶的技巧描述,因此第2版在此基础上进行了完善。 书中首先通过介绍官方Nginx的基本用法和配置规则,帮助读者了解一般Nginx模块的用法,然后重点介绍了女口何开发HTTP模块(含HTTP过滤模块)来得到定制化的Ng......一起来看看 《深入理解Nginx(第2版)》 这本书的介绍吧!