python究竟要不要使用多线程
閱讀目錄
- 1. 先來(lái)看兩個(gè)例子
- 2. python虛擬機(jī)機(jī)制如何控制代碼執(zhí)行?
- 3. python多線程究竟有沒(méi)有用?
- 4. python多進(jìn)程執(zhí)行原理
在總結(jié)concurrent.futures庫(kù)之前先來(lái)弄明白三個(gè)問(wèn)題:回到頂部(1)python多線程究竟有沒(méi)有用?
(2)python虛擬機(jī)機(jī)制如何控制代碼的執(zhí)行?
(3)python中多進(jìn)程處理原理是怎么樣的?
1. 先來(lái)看兩個(gè)例子
(1)例1
分別用單線程、使用多線程、使用多進(jìn)程三種方法對(duì)最大公約數(shù)進(jìn)行計(jì)算
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import time
def gcd(pair):??? a, b = pair
??? low = min(a, b)
??? for i in range(low, 0, -1):
??????? if a % i == 0 and b % i == 0:
??????????? return i numbers = [
??? (1963309, 2265973), (1879675, 2493670), (2030677, 3814172),
??? (1551645, 2229620), (1988912, 4736670), (2198964, 7876293)
if __name__ == '__main__':# 不使用多線程和多進(jìn)程start = time.time()results = list(map(gcd,numbers))end = time.time()print('未使用--timestamp:{:.3f} second'.format(end-start))#使用多線程start = time.time()pool = ThreadPoolExecutor(max_workers=3)results = list(pool.map(gcd,numbers))end = time.time()print('使用多線程--timestamp:{:.3f} second'.format(end-start))#使用多進(jìn)程start = time.time()pool = ProcessPoolExecutor(max_workers=3)results = list(pool.map(gcd,numbers))end = time.time()print('使用多進(jìn)程程--timestamp:{:.3f} second'.format(end-start)) 輸出:
之前線程數(shù)和進(jìn)程說(shuō)都為3,現(xiàn)在修改為4再測(cè)試
為了更能說(shuō)明問(wèn)題,將線程數(shù)和進(jìn)程說(shuō)繼續(xù)增加為5
至于區(qū)別,大家自己感受,測(cè)試的條件(計(jì)算過(guò)于簡(jiǎn)單)、測(cè)試的環(huán)境都會(huì)影響測(cè)試結(jié)果
(2)例2
同樣分別用單線程、使用多線程、使用多進(jìn)程三種方法對(duì)網(wǎng)頁(yè)進(jìn)行爬蟲(chóng),只是簡(jiǎn)單的返回status_code
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import time
import requestsdef download(url):headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0','Connection':'keep-alive','Host':'example.webscraping.com'}response = requests.get(url, headers=headers)return(response.status_code)if __name__ == '__main__':urllist = ['http://example.webscraping.com/places/default/view/Afghanistan-1','http://example.webscraping.com/places/default/view/Aland-Islands-2','http://example.webscraping.com/places/default/view/Albania-3','http://example.webscraping.com/places/default/view/Algeria-4','http://example.webscraping.com/places/default/view/American-Samoa-5']start = time.time() result = list(map(download, urllist))end = time.time()print('status_code:',result)print('未使用--timestamp:{:.3f}'.format(end-start))pool = ThreadPoolExecutor(max_workers = 3)start = time.time() result = list(pool.map(download, urllist))end = time.time()print('status_code:',result)print('使用多線程--timestamp:{:.3f}'.format(end-start))pool = ProcessPoolExecutor(max_workers = 3)start = time.time() result = list(pool.map(download, urllist))end = time.time()print('status_code:',result)print('使用多進(jìn)程程--timestamp:{:.3f}'.format(end-start)) ? 輸出:
一下就看出了區(qū)別
回到頂部2. python虛擬機(jī)機(jī)制如何控制代碼執(zhí)行?
對(duì)于python來(lái)說(shuō),作為解釋型語(yǔ)言,Python的解釋器必須做到既安全又高效。我們都知道多線程編程會(huì)遇到的問(wèn)題,解釋器要留意的是避免在不同的線程操作內(nèi)部共享的數(shù)據(jù),同時(shí)它還要保證在管理用戶線程時(shí)保證總是有最大化的計(jì)算資源。python是通過(guò)使用全局解釋器鎖來(lái)保護(hù)數(shù)據(jù)的安全性。
python 代碼的執(zhí)行由python虛擬機(jī)來(lái)控制,即Python先把代碼(.py文件)編譯成字節(jié)碼(字節(jié)碼在Python虛擬機(jī)程序里對(duì)應(yīng)的是 PyCodeObject對(duì)象,.pyc文件是字節(jié)碼在磁盤(pán)上的表現(xiàn)形式),交給字節(jié)碼虛擬機(jī),然后虛擬機(jī)一條一條執(zhí)行字節(jié)碼指令,從而完成程序的執(zhí)行。 python在設(shè)計(jì)的時(shí)候在虛擬機(jī)中,同時(shí)只能有一個(gè)線程執(zhí)行。同樣地,雖然python解釋器中可以運(yùn)行多個(gè)線程,但在任意時(shí)刻,只有一個(gè)線程在解釋器 中運(yùn)行。而對(duì)python虛擬機(jī)的訪問(wèn)由全局解釋器鎖來(lái)控制,正是這個(gè)鎖能保證同一時(shí)刻只有一個(gè)線程在運(yùn)行。
在多線程的環(huán)境中,python虛擬機(jī)按一下 方式執(zhí)行:
(1)設(shè)置GIL(global interpreter lock)
(2)切換到一個(gè)線程執(zhí)行
(3)運(yùn)行:指定數(shù)量的字節(jié)碼指令、線程主動(dòng)讓出控制(可以調(diào)用time.sleep(0))
(4)把線程設(shè)置為睡眠狀態(tài)
(5)解鎖GIL
(6)再次重復(fù)以上步驟。
GIL的特性,也就導(dǎo)致了python不能充分利用多核cpu。而 對(duì)面向I/O的(會(huì)調(diào)用內(nèi)建操作系統(tǒng)C代碼的)程序來(lái)說(shuō),GIL會(huì)在這個(gè)I/O調(diào)用之前被釋放,以允許其他線程在這個(gè)線程等待I/O的時(shí)候運(yùn)行。如果線程 并未使用很多I/O操作,它會(huì)在自己的時(shí)間片一直占用處理器和GIL。
回到頂部3. python多線程究竟有沒(méi)有用?
通過(guò)前面的例子和python虛擬機(jī)制的理解對(duì)多線程的使用應(yīng)該很清楚了,I/O密集型python程序比計(jì)算密集型的程序更能充分利用多線 程的好處。 總之,在計(jì)算密集型的程序中不要python多線程,使用python多進(jìn)程進(jìn)行并發(fā)編程,就不會(huì)有GIL這種問(wèn)題存在,并且也能充分利用多核cpu。
(1)GIL不是bug,Guido也不是水平有限才留下這么個(gè)東西。龜叔曾經(jīng)說(shuō)過(guò),嘗試不用GIL而用其他的方式來(lái)做線程安全,結(jié)果python語(yǔ)言整體效率又下降了一倍,權(quán)衡利弊,GIL是最好的選擇——不是去不掉,而是故意留著的
(2)想讓python計(jì)算速度快起來(lái),又不想寫(xiě)C,用pypy吧,這才是真正的大殺器
(3)可以使用協(xié)程來(lái)提高cpu的利用率,使用multiprocessing和gevent
回到頂部4. python多進(jìn)程執(zhí)行原理
ProcessPoolExecutor類會(huì)利用multiprocessing模塊所提供的底層機(jī)制,以例2作為例子描述下多進(jìn)程執(zhí)行流程:
(1)把urllist列表中的每一項(xiàng)輸入數(shù)據(jù)都傳給map
(2)用pickle模塊對(duì)數(shù)據(jù)進(jìn)行序列化,將其變成二進(jìn)制形式
(3)通過(guò)本地套接字,將序列化之后的數(shù)據(jù)從解釋器所在的進(jìn)程發(fā)送到子解釋器所在的進(jìn)程
(4)在子進(jìn)程中,用pickle對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行反序列化,將其還原成python對(duì)象
(5)引入包含download函數(shù)的python模塊
(6)各個(gè)子進(jìn)程并行的對(duì)各自的輸入數(shù)據(jù)進(jìn)行計(jì)算
(7)對(duì)運(yùn)行的結(jié)果進(jìn)行序列化操作,將其轉(zhuǎn)變成字節(jié)
(8)將這些字節(jié)通過(guò)socket復(fù)制到主進(jìn)程之中
(9)主進(jìn)程對(duì)這些字節(jié)執(zhí)行反序列化操作,將其還原成python對(duì)象
(10)最后把每個(gè)子進(jìn)程所求出的計(jì)算結(jié)果合并到一份列表之中,并返回給調(diào)用者。
multiprocessing開(kāi)銷比較大,原因就在于:主進(jìn)程和子進(jìn)程之間通信,必須進(jìn)行序列化和反序列化的操作
總結(jié)
以上是生活随笔為你收集整理的python究竟要不要使用多线程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python多线程(3)——Queue模
- 下一篇: Adam那么棒,为什么还对SGD念念不忘