一、前言
Python由於GIL(全局鎖)的存在,不能發揮多核的優勢,其性能一直飽受詬病。然而在IO密集型的網絡編程裏,異步處理比同步處理能提升成百上千倍的效率,可以彌補Python性能方面的短板。
-
python3.0時代,標準庫裏的異步網絡模塊:select(非常底層)
-
python3.0時代,第三方異步網絡庫:Tornado
-
python3.4時代,asyncio:支持TCP,子進程.直接內置了對異步IO的支持。
現有的python 異步框架:tornado、fasapi 、django-3.0 asgi、aiohttp... python 中的主流django框架都在往異步靠攏。 不得不說,python要進入異步的時代了,那我也得趕緊學一學啊!
二、什麼是協程
協程不是計算機提供的,計算機提供了進程和線程的概念,而協程是我們程序員人爲去實現的。也叫微線程,是用戶態上下文切換的一種技術。通過一個線程去代碼之間遊走切換運行。可能有點難以理解,下面就從協程的發展演進中來體會什麼是協程。
三、協程的發展
- greenlet 早期模塊
- yield 關鍵字
- asynico 裝飾器(py.34)
- asny、await 關鍵字(py3.5)[官方推薦]
1、greenlet實現協程
# 引入包前需要先安裝:pip3 install greenlet
from greenlet import greenlet
def func1():
print(1) # 第2步 輸出1
gr2.switch() # 第3步 切換到 func2 函數執行
print(2) # 第6步 輸出2
gr2.switch() # 第7步 切換到 func2 函數執行
def func2():
print(3) # 第4步 輸出3
gr1.switch() # 第5步 切換到 func1 函數執行
print(4) # 第8步 輸出4
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch() # 第1步:去執行func1 函數
結果:
1
3
2
4
2、yield 關鍵字
def func1():
yield 1
yield from func2()
yield 2
def func2():
yield 3
yield 4
f1 = func1()
for item in f1:
print(item)
結果:
1
3
4
2
yeild 如果不瞭解,可以去先看一下python生成器部分的內容。
3、重頭戲 asyncio
必須在python3.4及之後的版本才能用
3.1 asyncio.coroutine 裝飾器 (python3.4版本)
import asyncio
@asyncio.coroutine
def func1():
print(1)
# 現在我用的是 sleep(2) 我要是換成 網絡請求 意義就比重大了
yield from asyncio.sleep(2) # 遇到IO耗時操作,自動化切換到tasks中的其他任務
print(2)
@asyncio.coroutine
def func2():
print(3)
yield from asyncio.sleep(2)
print(4)
tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
# 本來執行程序時應該等待 4 秒,但實際執行時間也就是2秒多一點,沒有等待4秒
注意:遇到io阻塞會自動切換。asyncio.sleep(2)就是模擬阻塞兩秒的操作。
結果:
1
3
2
4
★3.2 async & await 關鍵字(Python3.5 及以後的版本才能用)
當前官方推薦的方式
import asyncio
async def func1():
print(1)
await asyncio.sleep(2)
print(2)
async def func2():
print(3)
await asyncio.sleep(2)
print(4)
tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
四、實例
在一個線程中如果遇到io等待的時間,線程不會一直等待,利用空閒的時間去執行其他的任務 如果那個任務有結果了 在回去拿結果。
下面是不使用異步與使用異步編程對比的案例:
網絡io請求獲取三張圖片
1、不使用異步:
# pip3 install requests
import requests
def download_image(url):
print(f'開始下載圖片:{url}')
response = requests.get(url)
print(f'下載完成')
file_name = url.rsplit("/")[-1]
with open(file_name,'wb',) as f:
f.write(response.content)
if __name__ == '__main__':
url_list = [
'http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg',
'http://a2.att.hudong.com/36/48/19300001357258133412489354717.jpg',
'http://a0.att.hudong.com/64/76/20300001349415131407760417677.jpg',
]
for url in url_list:
download_image(url)
結果:
開始下載圖片:http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg
下載完成
開始下載圖片:http://a2.att.hudong.com/36/48/19300001357258133412489354717.jpg
下載完成
開始下載圖片:http://a0.att.hudong.com/64/76/20300001349415131407760417677.jpg
下載完成
看結果 是安順序執行的 如果沒哥請求是兩分鐘的話 那就需要足足等待6分鐘。
如果我們用 異步做就會大大的提高性能。
2、使用異步
import asyncio
import aiohttp
async def fetch(session,url):
print(f'發送請求{url}')
async with session.get(url,verify_ssl=False) as response:
content = await response.read()
file_name = url.rsplit('/')[-1]
with open(file_name,'wb') as f:
f.write(content)
print('下載完成')
async def main():
async with aiohttp.ClientSession() as session:
url_list = [
'http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg',
'http://a2.att.hudong.com/36/48/19300001357258133412489354717.jpg',
'http://a0.att.hudong.com/64/76/20300001349415131407760417677.jpg',
]
tasks = [
asyncio.create_task(fetch(session,url)) for url in url_list
]
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(main())
結果:
發送請求http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg
發送請求http://a2.att.hudong.com/36/48/19300001357258133412489354717.jpg
發送請求http://a0.att.hudong.com/64/76/20300001349415131407760417677.jpg
下載完成
下載完成
下載完成