python多線程學習(一)


python多線程、多進程 初探

原先剛學Java的時候,多線程也學了幾天,后來一直沒用到。然后接觸python的多線程的時候,貌似看到一句”python多線程很雞肋“,於是乎直接跳過了多線程的學習。

接觸爬蟲,才開始用到多進程這個東西。

既然用到了,就系統地學吧。先來python的,再總結一下Java的。

什么是線程和進程

很經典的一個解釋是“進程是資源分配的最小單位,線程是CPU調度的最小單位“。
比如我們在任務管理器中看到的就是進程,例如qq.exe,qq就是一個進程。打開qq后,啟動多條線程,各自負責文件下載、更新朋友圈、聊條等功能。

每個進程,都有自己的地址空間、內存、數據棧以及其他用於跟蹤執行的輔助數據。進程可以通過派生(fork或spawn)新的進程來執行其他任務,不過這個新的進程是獨立的,所有進程之間只能采用IPC(進程間通信)的方式共享信息。

線程,於進程類似,有時候也稱為輕量級進程,可以把他們 視為一個主進程中並行運行的一些”迷你進程“。同一個進程下的線程共享資源。
以前單核CPU時代,多線程用處並不多。進程和線程沒法做到真正的並行運算,而如今多核時代,真正能實現多條線程一起跑。

線程於進程的對比

對比維度 進程 線程
數據共享、同步 IPC共享數據較復雜,同步簡單 同進程下線程數據共享,但同步復雜
可靠性 進程間互相獨立 一個線程壞掉可能使整個進程掛掉
資源消耗 獨立資源,消耗多。進程間切換慢 切換快,共享資源,資源消耗少

總結,進程和線程還可以類比為火車和車廂:

線程在進程下行進(單純的車廂無法運行)

一個進程可以包含多個線程(一輛火車可以有多個車廂)

不同進程間數據很難共享(一輛火車上的乘客很難換到另外一輛火車,比如站點換乘)

同一進程下不同線程間數據很易共享(A車廂換到B車廂很容易)

進程要比線程消耗更多的計算機資源(采用多列火車相比多個車廂更耗資源)

進程間不會相互影響,一個線程掛掉將導致整個進程掛掉(一列火車不會影響到另外一列火車,但是如果一列火車上中間的一節車廂着火了,將影響到該趟火車的所有車廂)

進程可以拓展到多機,進程最多適合多核(不同火車可以開在多個軌道上,同一火車的車廂不能在行進的不同的軌道上)

進程使用的內存地址可以上鎖,即一個線程使用某些共享內存時,其他線程必須等它結束,才能使用這一塊內存。(比如火車上的洗手間)-”互斥鎖(mutex)”

進程使用的內存地址可以限定使用量(比如火車上的餐廳,最多只允許多少人進入,如果滿了需要在門口等,等有人出來了才能進去)-“信號量(semaphore)”

p ython的多線程為何雞肋

因為GIL,全局解釋鎖機制,python的多線程是偽多線程。

准確來說,GIL並不是python的機制,而是CPython解釋器(主流解釋器)的機制(比如Jpython就沒有GIL)。

什么是GIL

其實有點類似於單核CPU執行多進程的過程,雖然可以有多個進程,但只有一個CPU啊,一個進程執行的時候別的進程只能等,只是快速切換執行感覺上是一起執行罷了。現在多核CPU能做到真正的並行

在 Cpython 解釋器(Python語言的主流解釋器)中,有一把全局解釋鎖(Global Interpreter Lock),在解釋器解釋執行 Python 代碼時,先要得到這把鎖。鎖只有一把,一個線程執行的時候,別的線程也得等,多核CPU也沒用。(真正的多線程,比如Java的多線程,在多核情況下,可以實現真正的多條線程同步運行)

1.設置GIL

2.切換到一個線程去執行

3.運行

指定數量的字節碼指令

線程主動讓出控制(可以調用time.sleep(0))

4.把線程設置完睡眠狀態

5.解鎖GIL

6.再次重復以上步驟

線程什么時候會讓出全局鎖呢?一是執行滿 100 tick(可以理解為100單位的指令),二是遇見了阻塞(比如I/O)。

考慮兩種情況

  1. 計算密集:這時,幾乎沒一個線程都是執行滿100tick后讓出鎖的,所以和單線程跑沒啥區別,而且線程切換也需要時間,最終程序運行時間比起單線程反而更長。
  2. I/O密集型:一個線程只執行了幾tick,遇見I/O阻塞。這時候代碼已經解釋過了,可以獨立執行I/O過程,這是切換到另一個線程,同理。最終類似於真正的多線程,在I/O上節省的時間多余在線程切換消耗的時間,比單線程執行快。

因而,python多線程適合I/O密集型程序。

CPython 為什么要這樣設計

多線程有個問題,怎么解決共享數據的同步、一致性問題?因為,對於多個線程訪問共享數據時,可能有兩個線程同時修改一個數據情況,如果沒有合適的機制保證數據的一致性,那么程序最終導致異常,所以,Python之父就搞了個全局的線程鎖,不管你數據有沒有同步問題,反正一刀切,上個全局鎖,保證數據安全,簡單粗暴。

這種解決辦法放在90年代,其實是沒什么問題的,畢竟,那時候的硬件配置還很簡陋,單核 CPU 還是主流,多線程的應用場景也不多,大部分時候還是以單線程的方式運行,單線程不要涉及線程的上下文切換,效率反而比多線程更高(在多核環境下,不適用此規則)。所以,采用 GIL 的方式來保證數據的一致性和安全,未必不可取,至少在當時是一種成本很低的實現方式。

解決方案

因為Cpython的GIL,使python的多線程顯得雞肋。可以試着使用別的解釋器,但更常見的作法還是使用多進程。(當然,多進程必然消耗資源多一點)。

接下來學習

多線程主要用的是標准庫中的threading包,thread基本不用(主要是它在主線程結束后,其他線程會直接停止,有點類似於守護進程,顯然不安全)。

多進程使用標准庫中的multiprocessing。

接下來幾天閱讀官方文檔中的這幾節。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM