異步編程 - 協程

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
下載完成
下載完成
下載完成
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章