博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
异步编程 101: 是什么、小试Python asyncio
阅读量:6494 次
发布时间:2019-06-24

本文共 2840 字,大约阅读时间需要 9 分钟。

什么是异步编程?

注:本文说的同时是一个直观上感觉的概念,只是为了简化,不是严格意义上的同一时刻。

同步代码(synchrnous code)我们都很熟悉,就是运行完一个步骤再运行下一个。要在同步代码里面实现"同时"运行多个任务,最简单也是最直观地方式就是运行多个 threads 或者多个 processes。这个层次的『同时运行』多个任务,是操作系统协助完成的。 也就是操作系统的任务调度系统来决定什么时候运行这个任务,什么时候切换任务,你自己,作为一个应用层的程序员,是没办法进行干预的。

我相信你也已经听说了什么关于 thread 和 process 的抱怨:process 太重,thread 又要牵涉到很多头条的锁问题。尤其是对于一个 Python 开发者来说,由于(全局解释器锁)的存在,多线程无法真正使用多核,如果你用多线程来运行计算型任务,速度会更慢。

异步编程与之不同的是,值使用一个进程,不使用 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 timeimport requestsdef 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 timeimport asyncioimport aiohttpasync 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?

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

转载地址:http://ixyyo.baihongyu.com/

你可能感兴趣的文章
Cts框架解析(7)-任务运行的调度室
查看>>
SDN:软件定义网络
查看>>
1.1GTK+ 的简单程序HelloWorld
查看>>
一款基jquery超炫的动画导航菜单
查看>>
stm32时钟树讲解
查看>>
CSDN - 进程结束后new出的内存会回收吗?
查看>>
搭建Mantis 缺陷管理系统(转)
查看>>
一款基于jquery和css3的响应式二级导航菜单
查看>>
JMeter学习(二十三)关联
查看>>
【leetcode】Best Time to Buy and Sell 3 (hard) 自己做出来了 但别人的更好
查看>>
通过Navicat for MySQL远程连接的时候报错mysql 1130的解决方法
查看>>
sdut AOE网上的关键路径(spfa+前向星)
查看>>
C++编程思想重点笔记(上)
查看>>
【转发】什么时候该用委托,为什么要用委托,委托有什么好处
查看>>
[原]VS2012编译GLEW 1.11
查看>>
[AngularJS] Hijacking Existing HTML Attributes with Angular Directives
查看>>
关于android.view.WindowLeaked(窗体泄露)的解决方案
查看>>
微软职位内部推荐-Software Engineer II-News
查看>>
(转)I 帧和 IDR 帧的区别
查看>>
如何更快速加载你的JS页面
查看>>