做者簡介:html
姓名:黃志成(小黃)博客: 博客python
操做系統原理相關的書,基本都會提到一句很經典的話: "進程是資源分配的最小單位,線程則是CPU調度的最小單位"。web
線程是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務
好處 :安全
1.易於調度。 2.提升併發性。經過線程可方便有效地實現併發性。進程可建立多個線程來執行同一程序的不一樣部分。 3.開銷少。建立線程比建立進程要快,所需開銷不多。 4.利於充分發揮多處理器的功能。經過建立多線程進程,每一個線程在一個處理器上運行,從而實現應用程序的併發性,使每一個處理器都獲得充分運行。
在解釋python多線程的時候. 先和你們分享一下 python 的GIL 機制。多線程
Python代碼的執行由Python 虛擬機(也叫解釋器主循環,CPython版本)來控制,Python 在設計之初就考慮到要在解釋器的主循環中,同時只有一個線程在執行,即在任意時刻,只有一個線程在解釋器中運行。對Python 虛擬機的訪問由全局解釋器鎖(GIL)來控制,正是這個鎖能保證同一時刻只有一個線程在運行。併發
在多線程環境中,Python 虛擬機按如下方式執行:app
首先須要明確的一點是GIL並非Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。Python一樣一段代碼能夠經過CPython,PyPy,Psyco等不一樣的Python執行環境來執行。像其中的JPython就沒有GIL。然而由於CPython是大部分環境下默認的Python執行環境。因此在不少人的概念裏CPython就是Python,也就想固然的把GIL歸結爲Python語言的缺陷。因此這裏要先明確一點:GIL並非Python的特性,Python徹底能夠不依賴於GIL函數
還有,就是在作I/O操做時,GIL老是會被釋放。對全部面向I/O 的(會調用內建的操做系統C 代碼的)程序來講,GIL 會在這個I/O 調用以前被釋放,以容許其它的線程在這個線程等待I/O 的時候運行。若是是純計算的程序,沒有 I/O 操做,解釋器會每隔 100 次操做就釋放這把鎖,讓別的線程有機會執行(這個次數能夠經過 sys.setcheckinterval 來調整)若是某線程並未使用不少I/O 操做,它會在本身的時間片內一直佔用處理器(和GIL)。也就是說,I/O 密集型的Python 程序比計算密集型的程序更能充分利用多線程環境的好處。高併發
各個狀態說明:ui
可能有3種狀況從Running進入Blocked:
threading.Lock()不容許同一線程屢次acquire(), 而RLock容許, 即屢次出現acquire和release
上面介紹了這麼多理論.下面咱們用python提供的threading模塊來實現一個多線程的程序
threading 提供了兩種調用方式:
import threading def func(n): # 定義每一個線程要運行的函數 while n > 0: print("當前線程數:", threading.activeCount()) n -= 1 for x in range(5): t = threading.Thread(target=func, args=(2,)) # 生成一個線程實例,生成實例後 並不會啓動,須要使用start命令 t.start() #啓動線程
class MyThread(threading.Thread): # 繼承threading的Thread類 def __init__(self, num): threading.Thread.__init__(self) # 必須執行父類的構造方法 self.num = num # 傳入參數 num def run(self): # 定義每一個線程要運行的函數 while self.num > 0: print("當前線程數:", threading.activeCount()) self.num -= 1 for x in range(5): t = MyThread(2) # 生成實例,傳入參數 t.start() #啓動線程
兩種方式均可以調用咱們的多線程方法。
運行下面的代碼,看看結果.
import threading def func(n): while n > 0: print("當前線程數:", threading.activeCount()) n -= 1 for x in range(5): t = threading.Thread(target=func, args=(2,)) t.start() print("主線程:", threading.current_thread().name)
運行結果:
當前線程數: 2 當前線程數: 2 當前線程數: 2 當前線程數: 2 當前線程數: 2 當前線程數: 3 當前線程數: 3 當前線程數: 3 主線程: MainThread 當前線程數: 3 當前線程數: 3
那咱們如何阻塞子線程讓他們運行完,在繼續後面的操做呢.這個時候join()方法就派上用途了. 咱們改寫代碼:
import threading def func(n): while n > 0: print("當前線程數:", threading.activeCount()) n -= 1 threads = [] #運行的線程列表 for x in range(5): t = threading.Thread(target=func, args=(2,)) threads.append(t) # 將子線程追加到列表 t.start() for t in threads: t.join() print("主線程:", threading.current_thread().name)
join的原理就是依次檢驗線程池中的線程是否結束,沒有結束就阻塞直到線程結束,若是結束則跳轉執行下一個線程的join函數。
先看看這個:
一個進程能夠開啓多個線程,那麼多麼多個進程操做相同數據,勢必會出現衝突.那如何避免這種問題呢?
import threading,time num = 10 #共享變量 def func(): global num lock.acquire() # 加鎖 num = num - 1 lock.release() # 解鎖 print(num) threads = [] lock = threading.Lock() #生成全局鎖 for x in range(10): t = threading.Thread(target=func) threads.append(t) t.start() for t in threads: t.join()
經過 threading.Lock() 咱們能夠申請一個鎖。而後 acquire 方法進入臨界區.操做完共享數據 使用 release 方法退出.
臨界區的概念: 百度百科
在這裏補充一下:Python的Queue模塊是線程安全的.能夠不對它加鎖操做.
聰明的同窗 會發現一個問題? 我們不是有 GIL 嗎 爲何還要加鎖?
這個問題問的好!咱們下一節,將對這個問題進行探討.
GIL的鎖是對於一個解釋器,只能有一個thread在執行bytecode。因此每時每刻只有一條bytecode在被執行一個thread。GIL保證了bytecode 這層面上是線程是安全的.
可是若是你有個操做一個共享 x += 1,這個操做須要多個bytecodes操做,在執行這個操做的多條bytecodes期間的時候可能中途就換thread了,這樣就出現了線程不安全的狀況了。
總結:同一時刻CPU上只有單個執行流不表明線程安全。
互斥鎖 同時只容許一個線程更改數據,而Semaphore是同時容許必定數量的線程更改數據 ,好比廁全部3個坑,那最多隻容許3我的上廁所,後面的人只能等裏面有人出來了才能再進去。
import threading,time num = 10 def func(): global num lock.acquire() time.sleep(2) num = num - 1 lock.release() print(num) threads = [] lock = threading.BoundedSemaphore(5) #最多容許5個線程同時運行 for x in range(10): t = threading.Thread(target=func) threads.append(t) t.start() for t in threads: t.join() print("主線程:", threading.current_thread().name)
運行一下上面的代碼.你會很明顯的發現 每次只執行五個線程。
淺談多進程多線程的選擇: 文章連接python-多線程(原理篇): 文章連接
Python有GIL爲何還須要線程同步?: 文章連接