iT邦幫忙

4

python的asyncio模組(一):異步執行的好處

最近在工作中實作爬蟲,常常使用到python的asyncio模組,這是一個python3.4版才開始引入的異步框架標準模組,這在IO密集的任務中(比如說爬蟲),實在是非常好用,但同時他又有些複雜,所以才紀錄一下一些基本用法與原理,整理一下近期使用的心得。

在並發(concurrency)任務中,雖然python因為本身GIL的限制,但asyncio模組支援異步(asynchronous)執行的功能,雖然本身還是只能達到單核CPU的效能,無法達到完全的平行(parallelism)運算,但至少在一些常調動io的任務中,可以讓python不會因為io的調動而阻塞,讓程式的運行可以完全發揮單核CPU的效率。

在講解一些概念之前,先用python的requests模組來對一個url重複的做request,來看看非異步的程式有什麼效能上的限制:

# python3.5
# ubuntu 16.04

import requests
import time

url = 'https://www.google.com.tw/'

start_time = time.time()

def send_req(url):

    t = time.time()
    print("Send a request at",t-start_time,"seconds.")

    res = requests.get(url)

    t = time.time()
    print("Receive a response at",t-start_time,"seconds.")

for i in range(10):
    send_req(url)

上面的程式碼對google的入口網站做了十次的request,然後對發送request和接收response的時間做了紀錄。

以下是這個script執行的結果:
https://ithelp.ithome.com.tw/upload/images/20180929/20107274F29Y3aDYOm.png

從結果可以看出,從收到response到發送request的時間間隔非常短,連千分之一秒都不到,而反之,發送request到收到response的時間卻長很多,大約要0.2秒~0.3秒。

這是很正常的,當程式接收到網路來的response到繼續發送下一個request,中間所需要的時間就只是讓CPU去執行下一個python指令,但是發送request之後,到接收到response中間是需要等待網路另一端的server去回傳response,所需時間當然要長多了。

而這段等待sever回傳response的過程,就是io調度的過程,但這過程若要讓CPU掛在一旁等待,實在是太浪費時間了,所以才會引入異步執行的programing方式,讓io調度的過程中,程式不會掛在一旁等待,而是繼續執行下一條指令。

現在我們用asyncio模組以異步的方式重複上一段程式所做的事,程式的細節先不要理他,用執行結果來看有沒有為程式的速度帶來提升:

# python3.5
# ubuntu 16.04

import requests
import time
import asyncio

url = 'https://www.google.com.tw/'
loop = asyncio.get_event_loop()

start_time = time.time()

async def send_req(url):
    t = time.time()
    print("Send a request at",t-start_time,"seconds.")

    res = await loop.run_in_executor(None,requests.get,url)

    t = time.time()
    print("Receive a response at",t-start_time,"seconds.")

tasks = []

for i in range(10):
    task = loop.create_task(send_req(url))
    tasks.append(task)

loop.run_until_complete(asyncio.wait(tasks))

以下為執行結果:

https://ithelp.ithome.com.tw/upload/images/20180929/20107274ONcqTTlziS.png

原本上一段code總共需要2.58秒,但用了異步執行的方式後,把時間縮短到0.6秒,速度整整提升4倍,這裡還有一個值得注意的地方是,結果的前十行都是"Send a request",代表程式沒有因為io調度而被掛在一旁,而是繼續把剩下的request發完,這就是異步執行所達到的效果。

當然以上只是很粗略的說明異步執行為io密集任務所帶來的好處,其中還有非常多的細節以及asyncio的用法留待以後探討。

若有錯誤請多多指教。

參考資料:
並發(concurrency)和並行(parallelism)
https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/

https://www.jianshu.com/p/b5e347b3a17c


尚未有邦友留言

立即登入留言