本文首发于知乎
异步是继多线程、多进程以后第三种实现并发的方式,主要用于IO密集型任务的运行效率提高。python中的异步基于yield
生成器,在讲解这部分原理以前,咱们先学会异步库asyncio的使用。html
本文主要讲解asyncio
模块的通用性问题,对一些函数细节的使用就简单略过。python
本文分为以下部分编程
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秒。网络
上面代码一些约定俗成的用法记住就好,如session
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
,当前当作彻底等价便可,因此平时使用用上面哪一种均可以。并发
上面是最常看到的两种使用方式,这里列出来保证读者在看其余文章时不会发蒙。app
另外,两者实际上是有细微差异的异步
gather
更擅长于将函数聚合在一块儿wait
更擅长筛选运行情况细节能够参考这篇回答async
与以前学过的多线程、多进程相比,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 concurrent.futures as cf # 多加一个模块
import asyncio
import time
def myfun(i):
print('start {}th'.format(i))
time.sleep(1)
print('finish {}th'.format(i))
async def main():
with cf.ThreadPoolExecutor(max_workers = 10) as executor: # 设置10个线程
loop = asyncio.get_event_loop()
futures = (
loop.run_in_executor(
executor, # 按照10个线程来执行
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())
复制代码
用这种方法实现requests
异步爬虫代码以下
import concurrent.futures as cf
import asyncio
import requests
from bs4 import BeautifulSoup
def get_title(i):
url = 'https://movie.douban.com/top250?start={}&filter='.format(i*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)
async def main():
with cf.ThreadPoolExecutor(max_workers = 10) as executor:
loop = asyncio.get_event_loop()
futures = (
loop.run_in_executor(
executor,
get_title,
i)
for i in range(10)
)
for result in await asyncio.gather(*futures):
pass
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
复制代码
这种开启多个线程的方式也算异步的一种,下面一节详细解释。
如今咱们讲了一些异步的使用,是时候解释一些概念了
await
只使用一个线程就能够实现任务切换,另外一种是开启了多个线程,经过线程调度实现异步上面咱们是经过开启多个线程来实现requests
的异步,若是咱们想只用一个线程(用await
),就要换一个网页请求函数。
事实上要想用await
,必须是一个awaitable对象,这是不能使用requests
的缘由。而转化成awaitable对象这样的事固然也不用咱们本身实现,如今有一个aiohttp
模块能够将网页请求和asyncio
模块完美对接。使用这个模块改写代码以下
import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def get_title(i):
url = 'https://movie.douban.com/top250?start={}&filter='.format(i*25)
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(resp.status)
text = await resp.text()
print('start', i)
soup = BeautifulSoup(text, '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))
复制代码
专栏主页:python编程
专栏目录:目录
版本说明:软件及包版本说明