【Python拾遺】python異步編程——協程

一、前言

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
下載完成
下載完成
下載完成

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章