1. 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 只能由本線程進行操作。
2. 為什麼有人說 Python 的多線程是雞肋
多線程還是有用的,多進程有多進程的好處,多線程有多線程的好處。
多進程穩定,啟動時開銷大點,但如果你的運行時間遠大於多進程的時間,用多進程比較方便,如postgresql用多進程,chrome 多進程。
如果你只是想做個定時器樣的簡單東西,對穩定性要求低些,如vb,c#類似的定時器,用多線程吧,但線程的同步要注意了。python的線程更加類似定時器,python的線程不是真線程,但有的場合用這種定時器也能解決很多問題,因為開銷小,開啟也方便。
進程和線程,一個是重量級的,一個輕量級的,重量級的進程有保護區,進程上下文都是操作系統保護的,而線程是自己管理,需要一定的技術,不能保證在並發時的穩定性(多進程也不穩定,但很容易看出來,因為多出了進程容易發現),而python的更像是定時器,定時器有時也可以模擬線程,定時器多時的開銷比線程的開銷要小,真線程有下上文開銷,一個操作系統啟動多進程和多線程會達到切換飽和是有數量的,真線程或進程太多都會導致cpu佔用率居高不下,而定時器可以開n多。
很多東西不是一種比另外一種先進,而是一種互補的關系,計算機的計算單位切換有優點必有缺點,關鍵在找到合適的使用方式揚長避短。
3. 為什麼在Python里推薦使用多進程而不是多
最近在看Python的多線程,經常我們會聽到老手說:「Python下多線程是雞肋,推薦使用多進程!」,但是為什麼這么說呢?
要知其然,更要知其所以然。所以有了下面的深入研究:
首先強調背景:
1、GIL是什麼?
GIL的全稱是Global Interpreter Lock(全局解釋器鎖),來源是python設計之初的考慮,為了數據安全所做的決定。
2、每個CPU在同一時間只能執行一個線程(在單核CPU下的多線程其實都只是並發,不是並行,並發和並行從宏觀上來講都是同時處理多路請求的概念。但並發和並行又有區別,並行是指兩個或者多個事件在同一時刻發生;而並發是指兩個或多個事件在同一時間間隔內發生。)
在Python多線程下,每個線程的執行方式:
1、獲取GIL
2、執行代碼直到sleep或者是python虛擬機將其掛起。
3、釋放GIL
可見,某個線程想要執行,必須先拿到GIL,我們可以把GIL看作是「通行證」,並且在一個python進程中,GIL只有一個。拿不到通行證的線程,就不允許進入CPU執行。
在Python2.x里,GIL的釋放邏輯是當前線程遇見IO操作或者ticks計數達到100(ticks可以看作是Python自身的一個計數器,專門做用於GIL,每次釋放後歸零,這個計數可以通過 sys.setcheckinterval 來調整),進行釋放。
而每次釋放GIL鎖,線程進行鎖競爭、切換線程,會消耗資源。並且由於GIL鎖存在,python里一個進程永遠只能同時執行一個線程(拿到GIL的線程才能執行),這就是為什麼在多核CPU上,python的多線程效率並不高。
那麼是不是python的多線程就完全沒用了呢?
在這里我們進行分類討論:
1、CPU密集型代碼(各種循環處理、計數等等),在這種情況下,由於計算工作多,ticks計數很快就會達到閾值,然後觸發GIL的釋放與再競爭(多個線程來回切換當然是需要消耗資源的),所以python下的多