目錄python
在Cpython解釋器中,同一個進程下開啓的多線程,同一時刻只能有一個線程執行,沒法利用多核優點編程
首先須要明確的一點是GIL並非Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就比如C++是一套語言(語法)標準,可是能夠用不一樣的編譯器來編譯成可執行代碼。>有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也同樣,一樣一段代碼能夠經過CPython,PyPy,Psyco等不一樣的Python執行環境來執行。像其中的JPython就沒有GIL。然而由於CPython是大部分環境下默認的Python執行環境。因此在不少人的概念裏CPython就是Python,也就想固然的把GIL歸結爲Python語言的缺陷。因此這裏要先明確一點:GIL並非Python的特性,Python徹底能夠不依賴於GIL安全
GIL 本質上就是一把互斥鎖,將併發運行編程串行,以此來控制同一時間內共享的數據只能被一個任務鎖修改,進而保證數據的安全多線程
能夠確定的是,保護數據的安全,就應該加鎖併發
''' 驗證全局解釋器鎖 ''' import time from threading import Thread,current_thread n = 100 def task(): global n n2 = n # 全局解釋器鎖碰到 IO阻塞就切換cpu執行並解鎖 time.sleep(1) n = n2 - 1 print(n,current_thread().name) for line in range(100): t = Thread(target=task) t.start()
爲何有了解釋器的鎖來保證同一時間只能有一個線程來執行,爲何還須要有線程的lock鎖?app
首先,咱們要達成共識,鎖的目的就是爲了保護數據,同一時間只能有一個線程來修改共享的數據性能
而後,咱們就能夠獲得結論:保護不一樣的數據咱們須要加不一樣的鎖測試
那麼 GIL 與Lock是兩把鎖,保護的數據不同,GIL是解釋器裏的,保護的是解釋器級別的數據,好比垃圾回收的數據,Lock是保護用戶本身開發的應用程序的數據,很明顯GIL本身不負責這件事,只能用戶自定義加鎖處理,即Lock大數據
當咱們在沒有IO的程序裏面(純計算)不加鎖不會存在鎖的錯亂問題,由於GIL限制了同一時刻只能讓一個線程執行,因此不用加鎖ui
當咱們在IO密集的程序裏面,不加鎖會致使數據安全問題,由於程序遇到IO cpu會切斷使用權,讓另外一個線程執行,那麼這時候另外一個線程拿到的數據仍是以前的一份,不是最新的,致使全部線程修改數據不是正確的,引發數據安全問題,因此這個時候加上線程鎖來保證這個數據安全
站在兩個角度看問題
計算密集型:
單核:
開啓進程:消耗資源大
開啓線程:消耗資源小於進程
多核:
開啓進程:並行執行,效率高
開啓線程:併發執行,效率低
IO密集型:
單核:
開啓進程:消耗資源大
開啓線程:消耗資源小於進程
多核:
開啓進程:並行執行,效率小於多線程,由於遇到IO會當即切換CPU的執行權限
開啓線程:併發執行,效率高於多進程
# coding=utf-8 from multiprocessing import Process from threading import Thread import os import time # 計算密集型 def work1(): num = 0 for i in range(40000000): num += 1 # IO密集型 def work2(): time.sleep(1) if __name__ == '__main__': # 測試計算密集型 start_time = time.time() ls = [] for i in range(10): # 測試多進程 p = Process(target=work1) # 程序執行時間爲7.479427814483643 # 測試多線程 # p = Thread(target=work1) # 程序執行時間爲28.56563377380371 ls.append(p) p.start() for l in ls: l.join() end_time = time.time() print(f"程序執行時間爲{end_time - start_time}") # 測試計算密集型結論: # 在計算較小數據時候使用多線程效率高 # 在計算較大數據時候使用多進程效率高 # 測試IO密集型 start_time = time.time() ls = [] for i in range(10): # 測試多進程 # p = Process(target=work2) # 程序執行時間爲2.749157190322876 # 測試多線程 p = Thread(target=work2) # 程序執行時間爲1.0130579471588135 ls.append(p) p.start() for l in ls: l.join() end_time = time.time() print(f"程序執行時間爲{end_time - start_time}") # 測試IO密集型結論: # 使用多線程效率要比使用多進程效率高
計算密集型狀況下:
在計算較小數據時候使用多線程效率高
在計算較大數據時候使用多進程效率高
IO密集型狀況下:
使用多線程效率要比使用多進程效率高
高效執行程序:
使用多線程和多進程
這裏打個比方:
一個工人至關於cpu,工廠原材料就至關於線程。
此時計算至關於工人在幹活,I/O阻塞至關於爲工人幹活提供所需原材料的過程,工人幹活的過程當中若是沒有原材料了,則工人幹活的過程須要中止,直到等待原材料的到來。
若是你的工廠乾的大多數任務都要有準備原材料的過程(I/O密集型),那麼你有再多的工人,意義也不大,還不如一我的,在等材料的過程當中讓工人去幹別的活,
反過來說,若是你的工廠原材料都齊全,那固然是工人越多,效率越高
結論:
對計算機來講:CPU越多越好,可是對於IO來講,再多的CPU也沒用
對於程序來講:隨着CPU的增多執行效率確定會有所提升(無論提升多大,總會有所提升)
假設咱們有四個任務須要處理,處理方式確定是要玩出併發的效果,解決方案能夠是:
方案一:開啓四個進程
方案二:一個進程下,開啓四個線程
結果:
單核:
若四個任務是計算密集型,方案一增長了建立進程時間,方案二遠小於方案一,方案二勝
若四個任務是I/O密集型,方案一也增長了建立進程時間,且進程切換速度還不如線程,因此方 案二又勝
多核:
若四個任務是計算密集型,多核 開多個進程一塊兒計算是並行計算,在線程中執行用不上多核, 那麼方案一效率高,方案一勝
若四個任務是I/O密集型,再多的核也解決不了I/O問題,方案二勝
結論:
如今的計算機基本上都是多核,python對於計算密集型的程序來講,開多線程的效率並不能帶來多 大性能的提高,甚至還不如串行,可是對於I/O密集型的程序來講,開多線程效率就明顯提高
多線程用於I/O密集型:例如Socket、爬蟲、Web
多進程用於計算密集型,如金融分析,數據分析
# coding=utf-8 from threading import Lock,Thread,current_thread import time mutex_a = Lock() mutex_b = Lock() class MyThread(Thread): # 線程執行任務 def run(self): self.work1() self.work2() def work1(self): mutex_a.acquire() print(f"{self.name} 搶到了鎖a") mutex_b.acquire() print(f"{self.name} 搶到了鎖b") mutex_b.release() print(f"{self.name} 釋放了鎖b") mutex_a.release() print(f"{self.name} 釋放了鎖a") def work2(self): mutex_b.acquire() print(f"{self.name} 搶到了鎖b") # 模擬IO操做 time.sleep(1) mutex_a.acquire() print(f"{self.name} 搶到了鎖a") mutex_a.release() print(f"{self.name} 釋放了鎖a") mutex_b.release() print(f"{self.name} 釋放了鎖b") for i in range(2): t = MyThread() t.start() Thread-1 搶到了鎖a Thread-1 搶到了鎖b Thread-1 釋放了鎖b Thread-1 釋放了鎖a Thread-1 搶到了鎖b Thread-2 搶到了鎖a .....卡主了
開啓兩個線程以後,每一個線程會搶cpu執行,
第一個線程搶到了,執行work1,搶到鎖a、b,釋放a、b
再回來執行work2,搶到鎖b,碰到有IO阻塞,切換線程執行
到第二個線程,能夠搶到鎖a,可是鎖還在第一個線程手裏拿着
鎖b麼有被釋放,線程二拿不到鎖b,因而卡主了
解決死鎖問題,須要用到遞歸鎖
RLock:只有一把鑰匙,能夠提供多個線程去使用,每次使用會計數+1,只有計數爲0 的時候 才能真正釋放讓另外一個線程使用
能夠理解爲遇到IO操做以後,若是身上有這個遞歸鎖,必須先把這個遞歸鎖解開以後而後你再去作其餘事情。
# coding=utf-8 from threading import Lock,Thread,current_thread,RLock import time # mutex_a = Lock() # mutex_b = Lock() mutex_a = mutex_b = RLock() class MyThread(Thread): # 線程執行任務 def run(self): self.work1() self.work2() def work1(self): mutex_a.acquire() print(f"{self.name} 搶到了鎖a") mutex_b.acquire() print(f"{self.name} 搶到了鎖b") mutex_b.release() print(f"{self.name} 釋放了鎖b") mutex_a.release() print(f"{self.name} 釋放了鎖a") def work2(self): mutex_b.acquire() print(f"{self.name} 搶到了鎖b") # 模擬IO操做 time.sleep(1) mutex_a.acquire() print(f"{self.name} 搶到了鎖a") mutex_a.release() print(f"{self.name} 釋放了鎖a") mutex_b.release() print(f"{self.name} 釋放了鎖b") for i in range(2): t = MyThread() t.start() Thread-1 搶到了鎖a Thread-1 搶到了鎖b Thread-1 釋放了鎖b Thread-1 釋放了鎖a Thread-1 搶到了鎖b Thread-1 搶到了鎖a Thread-1 釋放了鎖a Thread-1 釋放了鎖b Thread-2 搶到了鎖a Thread-2 搶到了鎖b Thread-2 釋放了鎖b Thread-2 釋放了鎖a Thread-2 搶到了鎖b Thread-2 搶到了鎖a Thread-2 釋放了鎖a Thread-2 釋放了鎖b
互斥鎖:只有一把鎖,只能一我的去使用
信號量:能夠自定義鎖的數量,提供多我的使用
# coding=utf-8 from threading import Semaphore,Lock from threading import current_thread from threading import Thread import time # 信號量:提供5個鎖 sm = Semaphore(5) # 互斥鎖:提供一個鎖 mutex = Lock() def task(): sm.acquire() print(f"子線程{current_thread().name}") time.sleep(1) sm.release() if __name__ == '__main__': for i in range(10): t = Thread(target=task) t.start() # 五個五個執行 子線程Thread-1 子線程Thread-2 子線程Thread-3 子線程Thread-4 子線程Thread-5 子線程Thread-6 子線程Thread-7 子線程Thread-8 子線程Thread-9 子線程Thread-10
FIFO:先進先出
LIFO:後進先出
優先級隊列:根據參數中的數字字母排序,排在前面的優先級越高,優先取出
# coding=utf-8 import queue from multiprocessing import Queue # FIFO隊列:先進先出 q = queue.Queue() q.put(4) q.put(2) q.put(3) print(q.get()) print(q.get()) # 4 # 2 # LIFO隊列:後進先出 q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) # 3 # 2 # 優先級隊列 q = queue.PriorityQueue() q.put(3) q.put(2) q.put(11) print(q.get()) # 2