异步编程 101: 是什么、小试Python asyncio

栏目: Python · 发布时间: 5年前

内容简介:注:本文说的同时是一个直观上感觉的概念,只是为了简化,不是严格意义上的同一时刻。同步代码(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秒。

异步编程 101: 是什么、小试Python asyncio

异步版本

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:
异步编程 101: 是什么、小试Python asyncio

如果打印一下 visit_async() 返回值的类型可以看到,这是一个coroutine(协程)。

异步编程 101: 是什么、小试Python asyncio

正常的姿势是调用 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 asyncio

总结一下

事实上,这篇文章已经引出了异步编程中一个重要的概念:协程。『异步编程101』系列文章后面还会花很多篇幅说一说一下协程。

协程"同时"运行多个任务的基础是 函数可以暂停 (后面我们会讲到这一点是如何实现的,Python 中是通过 yield)。上面的代码中使用到了 asyncio 的 event_loop,它做的事情,本质上来说就是当函数暂停时,切换到下一个任务,当时机恰当(这个例子中是请求完成了)恢复函数让他继续运行(这有点像操作系统了)。

这相比使用多线程或多进程,把调度地任务交给操作系统,在性能上有极大的优势,因为不需要大量的 syscall。同时又解决了多线程数据共享带来的锁的问题。 而且作为一个应用程序开发者,你应该是要比操作系统更懂,哪些时候进行任务切换。

我个人觉得,新时代的程序员,有两点技能是非常重要的: 异步编程的能力和利用多核系统的能力。

觉得不错点个 star?

异步编程 101: 是什么、小试Python asyncio

我的公众号:全栈不存在的

异步编程 101: 是什么、小试Python asyncio

以上所述就是小编给大家介绍的《异步编程 101: 是什么、小试Python asyncio》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入理解Nginx(第2版)

深入理解Nginx(第2版)

陶辉 / 机械工业出版社 / 2016-2 / 99.00元

本书致力于说明开发Nginx模块的必备知识,第1版发行以后,深受广大读者的喜爱.然而由于Ng,nx功能繁多且性能强大,以致必须了解的基本技能也很庞杂,而第1版成书匆忙,缺失了几个进阶的技巧描述,因此第2版在此基础上进行了完善。 书中首先通过介绍官方Nginx的基本用法和配置规则,帮助读者了解一般Nginx模块的用法,然后重点介绍了女口何开发HTTP模块(含HTTP过滤模块)来得到定制化的Ng......一起来看看 《深入理解Nginx(第2版)》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具