目錄
概念介紹
測試環(huán)境
開始測試
測試【單進程單線程】
測試【多進程 并行】
測試【多線程 并發(fā)】
測試【協(xié)程 + 異步】
結(jié)果對比
繪圖展示
概念介紹
首先簡單介紹幾個概念:
進程和線程
進程就是一個程序在一個數(shù)據(jù)集上的一次動態(tài)執(zhí)行過程(數(shù)據(jù)集是程序在執(zhí)行過程中所需要使用的資源)讼溺。
線程也叫輕量級進程怒坯,它是一個基本的 CPU 執(zhí)行單元剔猿,是比進程更小的能獨立運行的基本單位嬉荆。
進程和線程的關(guān)系:
一個線程只能屬于一個進程,而一個進程可以有多個線程汪茧,但至少有一個線程舱污。
資源分配給進程扩灯,同一進程的所有線程共享該進程的所有資源。
CPU 分給線程珠插,即真正在 CPU 上運行的是線程丧失。
并行和并發(fā)
并行處理是計算機系統(tǒng)中能同時執(zhí)行兩個或更多個處理的一種計算方法。并行處理可同時工作于同一程序的不同方面琳拭,其主要目的是節(jié)省大型和復雜問題的解決時間白嘁。
并發(fā)處理指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間絮缅,且這幾個程序都是在同一個 CPU 上運行呼股,但任一個時刻點上只有一個程序在 CPU 上運行。
并發(fā)的關(guān)鍵是你有處理多個任務的能力吸奴,不一定要同時则奥。并行的關(guān)鍵是你有同時處理多個任務的能力读处。所以說唱矛,并行是并發(fā)的子集。多進程是并行的管闷,多線程是并發(fā)的渐北。
同步和異步
同步就是指一個進程在執(zhí)行某個請求的時候阿逃,若該請求需要一段時間才能返回信息,那么這個進程將會一直等待下去搀菩,直到收到返回信息才繼續(xù)執(zhí)行下去肪跋。
異步是指進程不需要一直等下去,而是繼續(xù)執(zhí)行下面的操作州既,不管其他進程的狀態(tài)吴叶。當有消息返回時系統(tǒng)會通知進程進行處理,這樣可以提高執(zhí)行的效率实束。
舉個例子逊彭,打電話時就是同步通信侮叮,發(fā)短息時就是異步通信。
測試環(huán)境
進行對比測試之前谷异,我們先來創(chuàng)建一個合適的實驗環(huán)境:
???????模擬一個需要等待一定時間才可以獲取返回結(jié)果的網(wǎng)頁锦聊。
如果直接用百度孔庭、CSDN 等站點的話,一方面響應太快怎抛、難以看出各種方法的差距芽淡,另一方面響應速度會受網(wǎng)速影響挣菲、每次發(fā)送請求獲取響應所需的時間不完全一致導致重復實驗結(jié)果差距較大掷邦,所以在此用 Flask 模擬一個本地慢速服務器抚岗。
flask_server.py 代碼如下:
from flask import Flask? ? # pip install flask
import time
app = Flask(__name__)
@app.route('/')
def index():
? ? time.sleep(3)? ? # 休眠 3 秒再返回結(jié)果
? ? return 'Hello!'
if __name__ == '__main__':
? ? app.run(threaded=True)? # 以多線程模式啟動服務
啟動之后宣蔚,F(xiàn)lask 服務默認在 127.0.0.1:5000 上運行认境,控制臺輸出結(jié)果如下:
* Serving Flask app "flask_server" (lazy loading)
* Environment: production
? WARNING: Do not use the development server in a production environment.
? Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
在瀏覽器中訪問 http://127.0.0.1:5000/ 等待 3 秒即會出現(xiàn) Hello! 頁面叉信,表明簡單的慢速服務器搭建完成!
開始測試
首先導入需要的模塊鉴未,以請求 10 次為例準備 urls铜秆,再定義一個 get_html_text() 函數(shù):
import requests
import time
# 用于多進程
from multiprocessing import Process
# 用于多線程
from threading import Thread
# 用于協(xié)程+異步
import asyncio
import aiohttp? ? ? # pip install aiohttp
urls = ['http://127.0.0.1:5000' for _ in range(10)]
def get_html_text(url):
? ? response = requests.get(url)
? ? return response.text
測試【單進程單線程】
start = time.time()
for url in urls:
? ? result = get_html_text(url)
? ? print(result)
end = time.time()
print('【單進程單線程】耗時:%s 秒' %(end - start))
結(jié)果如下:
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
【單進程單線程】耗時:30.15605854988098 秒
測試【多進程 并行】
start = time.time()
processes = []
for url in urls:
? ? p = Process(target=get_html_text, args=(url,))
? ? processes.append(p)
? ? p.start()
for p in processes:
? ? p.join()
? ? print('Hello!')
end = time.time()
print('【多進程? 并行】耗時:%s 秒' %(end - start))
結(jié)果如下(測試電腦為 4 核 CPU连茧,核心數(shù)越大加速越明顯):
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
【多進程? 并行】耗時:5.511234283447266 秒
測試【多線程 并發(fā)】
start = time.time()
threads = []
for url in urls:
? ? t = Thread(target=get_html_text, args=(url,))
? ? threads.append(t)
? ? t.start()
for t in threads:
? ? t.join()
? ? print('Hello!')
end = time.time()
print('【多線程? 并發(fā)】耗時:%s 秒' %(end - start))
結(jié)果如下:
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
【多線程? 并發(fā)】耗時:3.030653953552246 秒
測試【協(xié)程 + 異步】
# 因為 requests 模塊不支持異步操作啸驯,需要借助 aiohttp 模塊
async def get_html_text_async(url):
? ? async with aiohttp.ClientSession() as session:
? ? ? ? async with session.get(url) as response:
? ? ? ? ? ? text = await response.text()
? ? ? ? ? ? return text
start = time.time()
tasks = [asyncio.ensure_future(get_html_text_async(url)) for url in urls]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
for task in tasks:
? ? print(task.result())
end = time.time()
print('【協(xié)程 ++ 異步】耗時:%s 秒' %(end - start))
結(jié)果如下:
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
【協(xié)程 ++ 異步】耗時:3.046288251876831 秒
結(jié)果對比
len(urls)==1:
len(urls)==4:
len(urls)==10:
len(urls)==100:
單進程單線程是將 n 次請求順次執(zhí)行,每次要等待 3 秒才能返回結(jié)果宅楞,故耗時 3n+ 秒;
多進程-并行處理則利用 CPU 的多核優(yōu)勢距淫,在同一時間并行地執(zhí)行多個任務榕暇,可以大大提高執(zhí)行效率,但系統(tǒng)實現(xiàn)多進程前需要一些準備工作彤枢、將耗費大量時間。
多線程-并發(fā)處理和協(xié)程+異步的耗時由單進程單線程的 3n+ 秒變成了 3+ 秒!
前者是 n 個請求幾乎同時進行盟猖、幾乎同時得到響應返回結(jié)果换棚。
后者是每當請求任務遇到阻塞(time.sleep(3))時被掛起,n 個任務都處于掛起狀態(tài)后等待 3 秒娘汞,n 個請求幾乎同時都有了響應你弦,然后掛起的任務被喚醒接著執(zhí)行燎孟,輸出請求結(jié)果,最后耗時:3 秒?醭ァ(多出來的時間是 IO 時延)
注意:
搭建的實驗環(huán)境是慢速服務器萍程,若不進行 time.sleep(3) 休眠 3 秒再返回 而是立即響應的話兔仰,單進程單線程的實際耗時則會大大縮短乎赴,請求次數(shù)少的話甚至會超過多進程。
而且筆者在 Windows 4 核 CPU 環(huán)境下測試缔赠,最多開啟 4 個進程嗤堰,未能發(fā)揮多進程的真實實力。
另外告匠,多進程后专、多線程還可以通過進程池戚哎、線程池來實現(xiàn)嫂用,與文中方法耗時基本一致嘱函,故未做展示;多進程或多線程與協(xié)程異步IO結(jié)合的效率尚待測試疏唾。