51好读  ›  专栏  ›  dwzb

python异步asyncio模块的使用

dwzb  · 掘金  ·  · 2018-03-14 02:11

正文

python异步asyncio模块的使用

本文首发于 知乎
异步是继多线程、多进程之后第三种实现并发的方式,主要用于IO密集型任务的运行效率提升。python中的异步基于 yield 生成器,在讲解这部分原理之前,我们先学会异步库asyncio的使用。

本文主要讲解 asyncio 模块的通用性问题,对一些函数细节的使用就简单略过。

本文分为如下部分

  • 最简单的使用
  • 另一种常见的使用方式
  • 一个问题
  • 一般函数下的异步
  • 理解异步、协程
  • 单个线程的的异步爬虫

最简单的使用

import asyncio
async def myfun(i):
print('start {}th'.format(i))
await asyncio.sleep(1)
print('finish {}th'.format(i))
loop = asyncio.get_event_loop()
myfun_list = (myfun(i) for i in range(10))
loop.run_until_complete(asyncio.gather(*myfun_list))

这样运行,10次等待总共只等待了1秒。

上面代码一些约定俗成的用法记住就好,如

  • 要想异步运行函数,需要在定义函数时前面加 async
  • 后三行都是记住就行,到时候把函数传入

另一种常见的使用方式

上面是第一种常见的用法,下面是另外一种

import asyncio
async def myfun(i):
print('start {}th'.format(i))
await asyncio.sleep(1)
print('finish {}th'.format(i))
loop = asyncio.get_event_loop()
myfun_list = [asyncio.ensure_future(myfun(i)) for i in range(10)]
loop.run_until_complete(asyncio.wait(myfun_list))

这种用法和上面一种的不同在于后面调用的是 asyncio.gather 还是 asyncio.wait ,当前看成完全等价即可,所以平时使用用上面哪种都可以。

上面是最常看到的两种使用方式,这里列出来保证读者在看其他文章时不会发蒙。

另外,二者其实是有细微差别的

  • gather 更擅长于将函数聚合在一起
  • wait 更擅长筛选运行状况

细节可以参考 这篇回答

一个问题

与之前学过的多线程、多进程相比, asyncio 模块有一个非常大的不同:传入的函数不是随心所欲

  • 比如我们把上面 myfun 函数中的 sleep 换成 time.sleep(1) ,运行时则不是异步的,而是同步,共等待了10秒
  • 如果我换一个 myfun ,比如换成下面这个使用 request 抓取网页的函数
import asyncio
import requests
from bs4 import BeautifulSoup
async def get_title(a):
url = 'https://movie.douban.com/top250?start={}&filter='.format(a*25)
r = requests.get(url)
soup = BeautifulSoup(r.content, 'html.parser')
lis = soup.find('ol', class_='grid_view').find_all('li')
for li in lis:
title = li.find('span', class_="title").text
print(title)
loop = asyncio.get_event_loop()
fun_list = (get_title(i) for i in range(10))
loop.run_until_complete(asyncio.gather(*fun_list))

依然不会异步执行。

到这里我们就会想,是不是异步只对它自己定义的 sleep ( await asyncio.sleep(1) )才能触发异步?

一般函数下的异步

对于上述函数, asyncio 库只能通过添加线程的方式实现异步,下面我们实现 time.sleep 时的异步

import asyncio
import time
def myfun(i):
print('start {}th'.format(i))
time.sleep(1)
print('finish {}th'.format(i))
async def main():
loop = asyncio.get_event_loop()
futures = (
loop.run_in_executor(
None,
myfun,
i)
for i in range(10)
)
for result in await asyncio.gather(*futures):
pass
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

上面 run_in_executor 其实开启了新的线程,再协调各个线程。调用过程比较复杂,只要当模板一样套用即可。

上面10次循环仍然不是一次性打印出来的,而是像分批次一样打印出来的。这是因为开启的线程不够多,如果想要实现一次打印,可以开启10个线程,代码如下

import






请到「今天看啥」查看全文