Python異步編程
前言
現在是 Python3.5 以後已經進入異步時代
Python由於GIL(全局鎖)的存在,不能發揮多核的優勢,其性能一直飽受詬病。然而在IO密集型的網絡編程裏,異步處理比同步處理能提升成百上千倍的效率,彌補了Python性能方面的短板.
-
python3.0時代,標準庫裏的異步網絡模塊:select(非常底層)
-
python3.0時代,第三方異步網絡庫:Tornado
-
python3.4時代,asyncio:支持TCP,子進程.直接內置了對異步IO的支持。
現有的python 異步框架
- tornado、fasapi 、djanao-3.0 asgi、aiohttp 等等等。。。
python 中的主流django框架都在 往異步靠攏 不得不說 要進入異步的時代了 ,而我還不會異步編程那就可悲了。
核心重點 異步編程 來提高性能。
主要以 這以下三點來聊:
- 協程
- asyncio模塊進行異步編程
- 實戰案例
協程
協程不是計算機提供的,計算機提供了進程和線程的概念,而協程是我們程序員人爲。也叫微線程,用戶態上下文切換的一種技術。
通過一個線程去代碼之間遊走切換之間去運行。
例如:
普通的執行
def func1():
print(1)
print(2)
def func(2):
print(3)
print(4)
func1()
func2()
結果:
1
2
3
4
實現協程
- greenlet ,早期模塊
- yield 關鍵字
- asynico 裝飾器(py.34)
- asny、await 關鍵字(py3.5)[官方推薦]
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
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
2
4
yeild 這個實現的 瞭解即可
重頭戲 asyncio
必須在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 秒 執行時間其實也就是1秒 沒有等待4秒
注意⚠️:遇到io阻塞自動切換
結果
1
3
2
4
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))
那我們最常用的就是 async & await 關鍵字
協程的意義
在一個線程中如果遇到io等待的時間,線程不會一直等待,利用空閒的時間去執行其他的任務 如果那個任務有結果了 在回去拿結果。
案例:
網絡io請求獲取三張圖片
普通方式(同步編程)
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分鐘。
如果我們用 異步做就會大大的提高性能。
協程方式(異步編程)
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
下載完成
下載完成
下載完成