當前位置:
首頁 > 知識 > 關於 Python 多線程的一些思考

關於 Python 多線程的一些思考

作者:許鍵

來源:騰訊雲技術社區(https://www.qcloud.com/community/)

導語

在知乎等地方經常看到有人問,Python 的多線程是不是雞肋?為何我用多線程性能一點沒有提升,有時候性能反而下降?在這裡通過日常工作中遇到的問題以及自己的一些總結,來一探 Python 多線程究竟是不是雞肋;如果不是,那又該如何使用。

1、遇到的問題

工作中常用到 python 來分析文件,統計數據;隨著業務的發展,原先的代碼性能受到了一定的挑戰,下面根據兩個案例來講解在 python 的使用過程中,遇到的一些問題,以及自己的一些總結。

案例一:數據統計,將文件按照一定的邏輯統計匯總後,入庫到本地 db 中。

最開始的代碼流程框圖:

大概流程:

1、循環讀文件,按照一定格式將文本進行拆分計算;

2、根據指定的 key 來統一匯總數據;

3、入庫本地 DB,入庫時,會先查找 db 中是否存在這條記錄,然後再判斷是否插入 db 中;相當於這裡會有兩次 db 操作,一次查詢,一次寫入。

一開始業務量小,db 數據量少,整個流程耗時較短,在秒級能夠完成,隨著業務發展,所需時間也有秒級變成了分鐘級,十分鐘級別等。

測試 1

通過對其中的一個業務某天的數據進行測試,它的耗時主要分部為,讀文件(文件大小 1G 左右)耗時 2 秒,邏輯計算及匯總 58s,數據入庫 32s;總耗時大概在 1 分半鐘。

1.1 方案一

流水線形式的多線程

線程1負責讀取數據,然後通過 python 自帶的 Qeueue,Q1 傳遞數據給線程 2;

線程 2 負責邏輯計算,然後通過 Q2 傳遞數據給線程 3;

線程 3 負責匯總數據,然後通過 Q3 傳遞數據給線程 4;

線程 4 則入庫數據;

這個方案在實現之後立馬就被廢棄了,它的效率比單進程的效率低很多,通過查看系統調用之後,發現是因為多個線程一直在競爭鎖,以及線程切換導致其執行效率還不如單線程。

1.2 方案二

數據分片分段多線程

在不同的時機採用多線程來處理,同時盡量避免多線程對同一資源進行競爭,以減少鎖的切換帶來的消耗。因此這裡在邏輯計算和數據入庫階段分別採用數據分組,多線程執行的方式來進行處理。

分段一、邏輯計算和匯總,將內存讀到內存中後,按照線程數量,將數據切分成多塊,讓第 i 個線程 thread[i]處理第 i 份數據 data[i],最後再將計算得到的 4 份數據匯總,按照相同的 key 進行匯總;得到 sum_data。

分段二、將 sumdata 按照線程數量,切分成多份,同樣讓 thread[i]處理 sumdata[i]的數據,讓他們各自進行數據的查詢以及寫入操作。

測試 2

對同一個文件進行測試,讀文件耗時 2 秒,邏輯計算及匯總 62s,數據入庫 10s;總耗時大概在 70 多秒,相比最開始的單線程,時間大概下降了 20 多 s;但可以看出,邏輯計算的時間相比單線程確增加了不少,而入庫操作的時間減少了 20 多 s;這裡就引發一個問題,邏輯計算跟入庫的差異在哪裡?為何前面的多線程性能下降,而後面性能確大幅度提高。

這裡的原因究竟為何?

案例二

案例 2 的整體流程為,將幾份不同的數據源從 db 中取出來,按天取出,經過一定的整合後,匯總插入到一個目標 db 中。

一開始的代碼流程:

同樣最初的時候,需要整合的數據量較少,db 中的數據量也較少。隨著業務增長,每天需要處理的數據量也逐漸增加,並且 db 中的數據量也越來越大,處理的時間也從開始的秒級別也逐漸增加到分鐘級別,每次都是統一處理一個月的數據,整體耗時需要幾個小時。

測試 3

對某天的數據進行測試,結果為:取數據 整合 耗時 30s;插入數據耗時約 8 分鐘。

更改成以下模型:

入庫操作同樣需要先根據 key 查找當前 db 中是否存在該條數據,不存在則寫入。

測試 4

取數據 整合 耗時 30s;插入數據耗時約 2 分鐘。

更改之後性能大幅度提升,由原先的 8 分半鐘,縮減為不到 2 分半鐘左右,縮減的時間主要體現在入庫階段;

從以上兩個例子可以看到,當涉及 I/0 操作時,python 的多線程能發揮較好的性能;而當涉及到 CPU 密集型邏輯運算時,python 的多線程性能不升反降。這裡都是由於 python 的 GIL 在發揮的作用。

2、了解 python 的 GIL

這裡我們使用的解釋器為官方實現的 CPython,它是由 C 語言實現的 python 解釋器。同時還存在由 Java 實現的 Jython 解釋器,由.NET 實現的 IronPython 等解釋器。這裡我們主要是依據 CPython 來講解 GIL 鎖。

GIL,全稱 Global Interpreter Lock, 是一個全局解釋器鎖,當解釋器需要執行 python 代碼時,都必須要獲得這把鎖,然後才能執行。當解釋器在遇到到 I/O 操作時會釋放這把鎖。但如果當程序為 CPU 密集型時,解釋器會主動的每間隔 100 次操作就釋放這把 GIL 鎖,這樣別的線程有機會執行,具體的間隔次數是由 sys.setcheckinterval( number ) 來設定這個值,通過 sys.getcheckinterval() 返回這個值,默認為 100。所以,儘管 Python 的線程庫直接封裝操作系統的原生線程,但 Python 進程,在同一時間只會有一個獲得了 GIL 的線程在跑,其它的線程都處於等待狀態等著 GIL 的釋放。就這樣對於 CPU 密集型操作來說,多線程不但不會提升性能,還會因為線程切換,鎖競爭等導致性能的下降。

在我們上面的兩個例子中,當涉及到數據的查詢與插入時,都需要進行 I/O 交互,並且會等待資料庫伺服器返回,這個時候,線程會主動釋放鎖,其他線程能夠合理利用這個時間,來做同樣的事情。

知道了 GIL 之後,我們才能更加合理的使用 python 的多線程,並不是所有場景都適用於多線程。

同樣,Python 的多線程也並不是大家所說的雞肋,在適合的場景用上了,還是能夠起到驚艷的作用。

題圖:pexels,CC0 授權。

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python 的精彩文章:

免費直播教你如何零基礎入門Python Web?
Python 菜鳥教程
讓小烏龜可以唱歌——對Python turtle進行拓展!祝全天下的父親們父親節快樂
Python 編寫知乎爬蟲實踐

TAG:Python |

您可能感興趣

android 多線程編程
python threading中處理主進程和子線程的關係
PyQt5+ Python3 多線程通信
muduo——EventLoop處理線程安全的問題
高性能的 PHP 封裝的 HTTP Restful 多線程並發請求庫-MultiHttp
線程池ThreadPoolExecutor里Control state,ctl參數的分析(一)
線程池ThreadPoolExecutor里Control state,ctl參數的分析(二)
python筆記12-python多線程之事件
Python學習之進程和線程
python udp的應用 ,多線程實現聊天功能
風冷也能壓制線程撕裂者,Arctic Cooling展示Freezer 50 TR散熱器
Synchronized鎖在Spring事務管理下,為啥還線程不安全?
你懂ThreadPoolExecutor線程池技術嗎?看源碼你會有全新的認識
入門Python多線程/多進程編程
Android 進程和線程
python爬取youtube視頻 多線程 非中文自動翻譯
Jmeter如何設置線程數,ramp-up period,循環次數
Python並發:線程和鎖
由於ZombieLoad漏洞,英特爾的CPU超線程可能要涼了
SqlSessionTemplate是如何保證MyBatis中SqlSession的線程安全的?