Python Web學習筆記之多線程編程

本次給你們介紹Python的多線程編程,標題以下:java

  1. Python多線程簡介
  2. Python多線程之threading模塊
  3. Python多線程之Lock線程鎖
  4. Python多線程之Python的GIL鎖
  5. Python多線程之ThreadLocal
  6. 多進程與多線程比較
  7. 多進程與多線程比較之執行特色
  8. 多進程與多線程比較之切換
  9. 多進程與多線程比較之計算密集型和IO密集型

 

 

Python多線程簡介

一個進程由若干個線程組成,在Python標準庫中,有兩個模塊threadthreading提供調度線程的接口。介於thread是低級模塊,不少功能還不完善,咱們通常只會用到threading這個比較完善的高級模塊,所以這裏咱們只討論threading模塊的使用。編程

 

Python多線程之threading模塊

要啓動一個線程,咱們只須要把一個函數傳入Thread實例,而後調用start()運行,這個咱們以前操做進程調用Process實例的方式一模一樣。網絡

current_thread()函數用於返回當前線程的實例,主線程實例的名字爲MainThread,子線程的名字能夠在建立時給予,或者被默認給予Thread-1Thread-2這樣的名字。多線程

 

Python多線程之Lock線程鎖

多進程和多線程最大的區別就在於,對於多進程,同一個變量各自有一份拷貝存在於每一個進程,互不影響,而多線程否則,全部的線程共用全部的變量,所以,任何一個變量均可以被任意的一個線程修改。爲了不多個線程同時修改同一個變量這種危險狀況的出現。異步

首先咱們須要理解,多個線程同時修改一個變量這種狀況是怎麼出現的。編程語言

理論上來講,不論咱們如何調用函數change(),共享變量a的值都應該爲0,但實際上,由於兩個線程t1t2之間交替運行的次數過多,致使a的結果未必就是0了。異步編程

要理解這種狀況首先要簡單的瞭解一下CPU執行代碼時的底層工做原理:函數

在編程語言中,一行代碼在底層運行的狀況未必就是做爲一行來完成的,例如上面的代碼a = a + 1,CPU在處理時實際上的運行方式是先用一個臨時變量存儲a+性能

1的值,再把這個臨時變量的值賦給a,如果學習過arm開發,就能夠理解到,CPU在工做時的狀況其實是先將值a1分別存入兩個寄存器,而後將兩個寄存器的值進行加法運算並將結果存入第三個寄存器,以後再將第三個寄存器的值存入並覆蓋本來保存a的值的寄存器內。用代碼語言能夠做以下理解:學習

 

正所以,由於兩個線程都調用了各自的寄存器,或者說都有各自的臨時變量c3,那麼當t1t2交替運行時,就可能出現下述代碼所描述的狀況:

爲了不這種狀況的發生,咱們就須要提供線程鎖來確保:當一個線程得到了change()的調用權時,另外一個線程就不能在同時執行change()方法,直到鎖被釋放以後,得到了該鎖才能繼續進行修改。

咱們用threading.lock()方法建立一個線程鎖

這樣子,不管如何運行,結果都將是咱們預期的0。

當多個線程同時執行lock.acquire()時,只有一個線程可以成功地得到線程鎖,而後繼續執行代碼,其它線程只能等待鎖的釋放。同時得到鎖的線程必定要記得釋放,不然會成爲死線程。所以咱們會用try...finally...來確保鎖的釋放。然而,鎖的問題就是一方面讓本來多線程的任務實際上又變成了單線程的運行方式(儘管對於Python的僞多線程而言,這並不會形成什麼性能的降低),另外,又因爲能夠存在多個鎖,對於不一樣的線程可能會持有不一樣的鎖而且試圖獲取對方的鎖時,可能會形成死鎖,致使多個線程所有掛起,這時只能經過操做系統來強行終止。

 

Python多線程之Python的GIL鎖

對於一個多核CPU,它能夠同時執行多個線程。咱們能夠經過Windows提供的任務管理器看見CPU的資源佔用率,所以,當咱們提供一個無限循環的死線程時,CPU一核的佔用率就會提高到100%,如果提供兩個,就又會有一核的佔用率到100%。若是在java或者C中這麼作,那麼確實會發生這種狀況,可是,若是咱們在Python中這樣嘗試的話

 

能夠看到,從 multiprocessing.cpu_count()得知咱們有4個CPU,而後打印了4行說明已經執行了4個線程,這個時候咱們的CPU佔用率應該是滿的,但實際上

 

咱們從紅框中看到,狀況並不是如此。實際上哪怕咱們啓用再多的線程,CPU的佔用率也不會提升多少。這是由於儘管Python使用的是真正的線程,但Python的解釋器在執行代碼時有一個GIL鎖(Gloabal Interpreter Lock),不管是什麼Python代碼,一旦執行必然會得到GIL鎖,而後每執行100行代碼就會釋放GIL鎖使得其它線程有機會執行。GIL鎖實際上就給一個Python進程的全部線程都上了鎖,所以哪怕是再多的線程,在一個Python進程中也只能交替執行,也便是隻能使用一個核。

 

Python多線程之ThreadLocal

既然咱們已經知道,一個全局變量會受到全部線程的影響,那麼,咱們應該如何構建一個獨屬於這個線程的「全局變量」?換言之,咱們既但願這個變量在這個線程中擁有相似於全局變量的功能,又不但願其它線程可以調用它,以防止出現上面所述的問題,該怎麼作?

 

能夠看到,在這個子線程中,若是咱們但願函數do_task1()do_task2()能用到變量a,則必須將它做爲參數傳進去。

使用ThreadLocal對象即是用於解決這個問題的方法而免於繁瑣的操做,它由threading.local()方法建立:

 

咱們能夠認爲ThreadLocal的原理相似於建立了一個詞典,當咱們建立一個變量local_varient.a的時候其實是在local_varient這個詞典裏面建立了數個以threading.current_thread()爲關鍵字(當前線程),不一樣線程中的a爲值的鍵值對組成的dict,能夠參照下面這個例程:

 

結果與上面用ThreadLocal的例程是同樣的。固然,我在這裏只是試圖簡單的描述一下ThreadLocal的工做原理,由於實際上它的工做原理和咱們上面利用dict的例程並非徹底同樣的,由於ThreadLocal對象可供傳給的變量徹底不僅一個:

甚至local_varient.clocal_varient.d…均可以,沒有必定的數量限制。而dict中能用threading.current_thread()作關鍵字的鍵值對都只能有一個不是嗎。

 

進程和線程的比較

在初步瞭解進程和線程以及它們在Python中的運用方式以後,咱們如今來討論一下兩者的區別與利弊。

多進程與多線程比較之執行特色

首先,咱們簡單瞭解一下多任務的工做模式:一般咱們會將其設計爲Master-Worker 模式,Master負責分配任務,Worker負責執行任務,多任務環境下一般是一個Master對應多個Worker。

那麼多進程任務實現Master-Worker,主進程就是Master,其它進程是Worker。而多線程任務,主線程Master,子線程Worker。

先來講說多進程,多進程的優勢就在於,它的穩定性高。由於一個子進程的崩潰不會影響到其它子進程和主進程(主進程掛了仍是會全崩的)。但多進程的問題就在於,其建立進程的開銷過大,特別是Windows系統,其多進程的開銷要比使用fork()的Unix/Linux系統大的多得多。而且,對於一個操做系統自己而言,它可以同時運行的進程數也是有限的。

多線程模式佔用的資源消耗沒有多進程那麼大,所以它也每每會更快一些(但彷佛也不會快太多?但至少在Windows下多線程的效率每每要比多進程要高),並且,多線程模式與多進程模式正好相反,一個線程掛掉會直接讓進程內包括主線程的全部的線程都崩潰,由於全部線程共享進程的內存。在Windows系統中,若是咱們看到了這樣的提示「該程序執行了非法操做,即將關閉」,那每每就是由於某個線程出現問題致使整個進程的崩潰。

 

多進程與多線程比較之切換

在使用多進程或多線程的時候都應該考慮線程數或者進程數切換的開銷。不管是進程仍是線程,若是數量太多,那麼效率是確定上不去的。

由於操做系統在切換進程和線程時,須要先保存當前執行的現場環境(包括CPU寄存器的狀態,內存頁等),而後再準備另外一個任務的執行環境(恢復上次的寄存器狀態,切換內存頁等),才能開始執行新任務。這個過程雖然很快,但再快也是須要耗時的,所以一旦任務數量過於龐大,那麼浪費在準備環境的時間就也會很是巨大。

 

多進程與多線程比較之計算密集型和IO密集型

考慮多任務的類型也是咱們判斷如何構建工做模式的一個重要點。咱們能夠將任務簡單的分爲兩類:計算密集型IO密集型

計算密集型任務的特色是要進行大量的運算,消耗CPU資源,例如一些複雜的數學運算,或者是一些視頻的高清解碼運算等等,純靠CPU的計算能力來執行的任務。這種任務雖然也能夠用多任務模式來完成,但任務之間切換的消耗每每比較大,所以如果要高效的進行這類任務的運算,計算密集型任務同時進行的數量最好不要超過CPU的核心數。

而對於語言而言,代碼運行的效率對於計算密集型任務也是相當重要,所以,相似於Python這樣的高級語言每每不適合,而像C這樣的底層語言的效率就會更高。好在Python處理這類任務時用的每每是用C編寫的庫,但如果要本身實現這類任務的底層計算功能,仍是以C爲主比較好。

IO密集型的特色則是要進行大量的輸入輸出,涉及到網絡、磁盤IO的任務每每都是IO密集型任務,這類任務消耗CPU的資源並不高,每每時間都是花在等待IO操做完成,由於IO操做的速度每每都比CPU和內存運行的速度要慢不少。對於IO密集型任務,多任務執行提高的效率就會很高,但固然,任務數量仍是有一個限度的。

而對於這類任務使用的編程語言,Python這類開發效率高的語言就會更適合,由於能減小代碼量,而C語言效果就不好,由於寫起來很麻煩。

現代操做系統對IO操做進行了巨大的改進,其提供了異步IO的操做來實現單進程單線程執行多任務的方式,它在單核CPU上採用單進程模型能夠高效地支持多任務。而在多核CPU上也能夠運行多個進程(數量與CPU核心數相同)來充分地利用多核CPU。經過異步IO編程模型來實現多任務是目前的主流趨勢。而在Python中,單進程的異步編程模型稱爲協程

 

 

參考

相關文章
相關標籤/搜索