本文目錄一覽:
Python中級精華-創建線程池
目的:
我們想去創建一個工作者線程池來處理客戶端的連接,讓每個線程去處理各自的客戶,或者完成其他類型的工作。
方法:
在concurrent.futures庫中包含了ThreadPoolExecutor類可以實現這個目的。下面的例子是一個簡單TCP伺服器,使用線程池來處理客戶端:
同樣,可以拋開concurrent.futures中的ThreadPoolExecutor,直接手動創建線程池,如果藉助Queue則會變得容易:
當然又有一句很蹩腳的話,不建議各位這樣使用!!應該去使用concurrent.futures中的ThreadPoolExecutor,這麼做的優勢在於提交任務者可以很好地拿到處理後的結果:
討論一個大家都很容易想到的問題,有些人認為,應該在伺服器接收到一個客戶端連接時就去開闢一個線程來處理這個客戶端的事務,本質上來講這樣確實沒有問題,但是,萬一一個黑客用大量的客戶端去訪問服務時,有可能會因為開闢太多線程導致伺服器掛掉,這是初學者都會想到的思路,但是,一旦涉及多並發程序,切記一定要限制線程個數,為了系統穩定和安全。下面為各位展示初學者的代碼:
這裡也要注意,線程只適合做IO密集型的任務。綜上關於線程池的內容就介紹到這裡
Python多線程總結
在實際處理數據時,因系統內存有限,我們不可能一次把所有數據都導出進行操作,所以需要批量導出依次操作。為了加快運行,我們會採用多線程的方法進行數據處理, 以下為我總結的多線程批量處理數據的模板:
主要分為三大部分:
共分4部分對多線程的內容進行總結。
先為大家介紹線程的相關概念:
在飛車程序中,如果沒有多線程,我們就不能一邊聽歌一邊玩飛車,聽歌與玩 遊戲 不能並行;在使用多線程後,我們就可以在玩 遊戲 的同時聽背景音樂。在這個例子中啟動飛車程序就是一個進程,玩 遊戲 和聽音樂是兩個線程。
Python 提供了 threading 模塊來實現多線程:
因為新建線程系統需要分配資源、終止線程系統需要回收資源,所以如果可以重用線程,則可以減去新建/終止的開銷以提升性能。同時,使用線程池的語法比自己新建線程執行線程更加簡潔。
Python 為我們提供了 ThreadPoolExecutor 來實現線程池,此線程池默認子線程守護。它的適應場景為突發性大量請求或需要大量線程完成任務,但實際任務處理時間較短。
其中 max_workers 為線程池中的線程個數,常用的遍歷方法有 map 和 submit+as_completed 。根據業務場景的不同,若我們需要輸出結果按遍歷順序返回,我們就用 map 方法,若想誰先完成就返回誰,我們就用 submit+as_complete 方法。
我們把一個時間段內只允許一個線程使用的資源稱為臨界資源,對臨界資源的訪問,必須互斥的進行。互斥,也稱間接制約關係。線程互斥指當一個線程訪問某臨界資源時,另一個想要訪問該臨界資源的線程必須等待。當前訪問臨界資源的線程訪問結束,釋放該資源之後,另一個線程才能去訪問臨界資源。鎖的功能就是實現線程互斥。
我把線程互斥比作廁所包間上大號的過程,因為包間里只有一個坑,所以只允許一個人進行大號。當第一個人要上廁所時,會將門上上鎖,這時如果第二個人也想大號,那就必須等第一個人上完,將鎖解開後才能進行,在這期間第二個人就只能在門外等著。這個過程與代碼中使用鎖的原理如出一轍,這裡的坑就是臨界資源。 Python 的 threading 模塊引入了鎖。 threading 模塊提供了 Lock 類,它有如下方法加鎖和釋放鎖:
我們會發現這個程序只會列印「第一道鎖」,而且程序既沒有終止,也沒有繼續運行。這是因為 Lock 鎖在同一線程內第一次加鎖之後還沒有釋放時,就進行了第二次 acquire 請求,導致無法執行 release ,所以鎖永遠無法釋放,這就是死鎖。如果我們使用 RLock 就能正常運行,不會發生死鎖的狀態。
在主線程中定義 Lock 鎖,然後上鎖,再創建一個子 線程t 運行 main 函數釋放鎖,結果正常輸出,說明主線程上的鎖,可由子線程解鎖。
如果把上面的鎖改為 RLock 則報錯。在實際中設計程序時,我們會將每個功能分別封裝成一個函數,每個函數中都可能會有臨界區域,所以就需要用到 RLock 。
一句話總結就是 Lock 不能套娃, RLock 可以套娃; Lock 可以由其他線程中的鎖進行操作, RLock 只能由本線程進行操作。
python 線程池的使用
最近在做一個爬蟲相關的項目,單線程的整站爬蟲,耗時真的不是一般的巨大,運行一次也是心累,,,所以,要想實現整站爬蟲,多線程是不可避免的,那麼python多線程又應該怎樣實現呢?這裡主要要幾個問題(關於python多線程的GIL問題就不再說了,網上太多了)。
一、 既然多線程可以縮短程序運行時間,那麼,是不是線程數量越多越好呢?
顯然,並不是,每一個線程的從生成到消亡也是需要時間和資源的,太多的線程會佔用過多的系統資源(內存開銷,cpu開銷),而且生成太多的線程時間也是可觀的,很可能會得不償失,這裡給出一個最佳線程數量的計算方式:
最佳線程數的獲取:
1、通過用戶慢慢遞增來進行性能壓測,觀察QPS(即每秒的響應請求數,也即是最大吞吐能力。),響應時間
2、根據公式計算:伺服器端最佳線程數量=((線程等待時間+線程cpu時間)/線程cpu時間) * cpu數量
3、單用戶壓測,查看CPU的消耗,然後直接乘以百分比,再進行壓測,一般這個值的附近應該就是最佳線程數量。
二、為什麼要使用線程池?
對於任務數量不斷增加的程序,每有一個任務就生成一個線程,最終會導致線程數量的失控,例如,整站爬蟲,假設初始只有一個鏈接a,那麼,這個時候只啟動一個線程,運行之後,得到這個鏈接對應頁面上的b,c,d,,,等等新的鏈接,作為新任務,這個時候,就要為這些新的鏈接生成新的線程,線程數量暴漲。在之後的運行中,線程數量還會不停的增加,完全無法控制。所以,對於任務數量不端增加的程序,固定線程數量的線程池是必要的。
三、如何使用線程池
過去使用threadpool模塊,現在一般使用concurrent.futures模塊,這個模塊是python3中自帶的模塊,但是,python2.7以上版本也可以安裝使用,具體使用方式如下:
注意到:
concurrent.futures.ThreadPoolExecutor,在提交任務的時候,有兩種方式,一種是submit()函數,另一種是map()函數,兩者的主要區別在於:
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/303212.html