python併發之協程

概述

協程(Coroutine) 協程的作用,是在執行函數A時,可以隨時中斷,去執行函數B,然後中斷繼續執行函數A(可以自由切換)。但這一過程並不是函數調用(沒有調用語句),這一整個過程看似像多線程,然而協程只有一個線程執行。
協程有以下優勢:

  • 執行效率極高。因爲子程序切換(函數)不是線程切換,由程序自身控制,沒有切換線程的開銷。
  • 不需要多線程的鎖機制,因爲只有一個線程,也不存在同時寫變量衝突,在控制共享資源時也不需要加鎖,因此執行效率高很多。

協程的使用

Python2.x通過gevent三方庫實現協程的功能。
Python 3.4版本引入的標準庫async,直接內置了對異步IO的支持。
Python3.5版本引入了async/await語法。對比Python3.4有些語法的差異。

下面介紹協程使用是基於Python 3.5版本。

協程支持的基本單元是一個coroutine
await後一般接一個coroutine.await 後面跟的必須是一個Awaitable對象,或者實現了相應協議的對象Coroutine類繼承了Awaitable.
我們使用的asyncio.create_task asyncio.sleep都是一個coroutine.
協程遇到await會切換。

  • async 語法糖,標記爲coroutine類型。
  • await 語法糖,切換操作。後面跟一個coroutine.
  • asyncio.create_task 創建任務, 參數要是一個coro,返回一個task
  • asyncio.gather 將一組 task(序列),執行
  • asyncio.wait 將一組coro(list),轉化爲一個coro
  • asyncio.run 循環執行任務。參數要是一個coro

asyncio.run 相當於老版本代碼。
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(coro)
finally:
loop.close()

async def crawl_page_1(url):
    print("crawling {}".format(url))
    sleep_time = int(url.split('_')[-1])
    await asyncio.sleep(sleep_time)
    print('OK {}'.format(url))

async def main_1(urls):
    tasks = [asyncio.create_task(crawl_page_1(url)) for url in urls]
    for task in tasks:
        await task

#---------------test-----------------

if __name__ == '__main__':
    #main(['url_1', 'url_2', 'url_3', 'url_4'])
    asyncio.run(main_1(['url_1', 'url_2', 'url_3', 'url_4']))

Future

Python 中的 Futures 模塊,位於 concurrent.futuresasyncio 中,它們都表示帶有延遲的操作。表示一個操作的執行情況,包括是否完成 done(),返回結果result(),設置完成回調add_done_callback(fn)等。

線程池executor.submit(func)返回一個Future.

import requests
import time
import concurrent.futures
import threading
def download_one_future(url): 
    resp = requests.get(url) 
    print('Read {} from {}'.format(len(resp.content), url))

def download_all_future(sites): 
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: 
        to_do = [] 
        for site in sites: 
            future = executor.submit(download_one, site) 
            to_do.append(future) 
        #等任務結束
        for future in concurrent.futures.as_completed(to_do): 
            print('result=', future.result())
if __name__ == '__main__':
    start_time = time.perf_counter()
    download_all_future(sites)
    end_time = time.perf_counter()
    print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))

asyncio.create_taskasyncio.gather的用法

async def download_one_async(url): 
    async with aiohttp.ClientSession() as session: 
        async with session.get(url) as resp: 
            print('Read {} from {}'.format(resp.content_length, url))

async def download_all_async(sites): 
    tasks = [asyncio.create_task(download_one(site)) for site in sites] 
    await asyncio.gather(*tasks)
sites = [
        'https://baike.baidu.com/item/%E5%8C%BA%E5%9D%97%E9%93%BE/13465666?fr=aladdin',
        'https://baike.baidu.com/item/%E6%AF%94%E7%89%B9%E5%B8%81%E6%8C%96%E7%9F%BF%E6%9C%BA/12536531?fr=aladdin',
        'https://baike.baidu.com/item/%E5%BA%9E%E6%B0%8F%E9%AA%97%E5%B1%80',
        'https://baike.baidu.com/item/%E6%8A%97%E7%94%9F%E7%B4%A0'
    ]
    download_all_async(sites)
    print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))

asyncio.waitasyncio.run的用法

async def test(i):
	print("test_1",i)
	await asyncio.sleep(1)
	print("test_2",i)

tasks=[test(i) for i in range(5)]
asyncio.run(asyncio.wait(tasks))

參考資料

python協程

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