CIL鎖,GIL與執行緒池的區別,程序池和執行緒池,同步與非同步
什麼是GIL? 全域性直譯器鎖,是加在直譯器上的互斥鎖
GC是python自帶的記憶體管理機制,GC的工作原理:python中的記憶體管理使用的是應用計數,每個數會被加上一個整型的計數器,表示這個資料被引用的次數,當這個整數變為0時則表示該資料已經沒有人使用,成為了垃圾資料,當記憶體佔用達到某個閾值,GC會將其他執行緒掛起,然後執行垃圾清理操作,垃圾清理也是一串程式碼,也就需要一條執行緒來執行.
為什麼需要GIL?
由於CPython的記憶體管理機制是非執行緒安全,於是CPython就給直譯器加了一個鎖,解決了安全問題,但是降低了效率,另外,雖然有解決方案,但是由於牽涉太多,一旦修改則很多基於GIL的程式都需要修改,所以變成了歷史遺留問題.
GIL加鎖,解鎖的時機?
加鎖時機:在呼叫直譯器時立即加鎖
解鎖時機:①當前執行緒遇到IO時釋放 ②當前執行緒執行時間超過設定值時釋放,直譯器會檢測執行緒的執行時間,一旦到達某個閾值,通知執行緒儲存狀態切換執行緒.
GIL帶來的問題:即使是多核處理器下也無法真正的並行.
總結:
①在單核情況下,無論是IO密集型還是計算密集型,GIL都不會產生影響,而多執行緒開銷小,並且節約資源,所以使用多執行緒.
②在多核情況下,IO密集型會受到GIL的影響,但是很明顯IO速度遠比計算速度慢,所以兩者執行的時間差不多,基本可以忽略不計,而在這個情況下我們考慮到多執行緒開銷小,並且節約資源,所以多核情況下,IO密集型我們使用多執行緒.
③對於計算密集型,在多核情況下,CPython中多執行緒是無法並行的,為了解決這一弊端,Python推出了多程序技術,可以良好的利用多核處理器來完成計算的任務.
多執行緒用於IO密集型,如socket,爬蟲,web
多程序用於計算密集型,如金融分析
多程序與多執行緒效率對比:
現在的電腦都是多核系統 #多程序解決計算密集型 from multiprocessing import Process import time a = 10 def task(): for i in range(10000000): global a a +=1 a*10/2-3 if __name__ == '__main__': start = time.time() ps = [] for i in range(3): p = Process(target=task) p.start() ps.append(p) for p in ps: p.join() print(time.time()-start) 結果:5.455920934677124 #多執行緒解決計算密集型 from threading import Thread import time a = 10 def task(): for i in range(10000000): global a a +=1 a*10/2-3 if __name__ == '__main__': start = time.time() ts = [] for i in range(3): t = Thread(target=task) t.start() ts.append(t) for t in ts: t.join() print(time.time()-start) 結果:8.375339031219482 #多程序解決IO密集型 from multiprocessing import Process import time def task(): path =r'E:\python試學視訊\day27、28選課系統\11 測試程式2.mp4' with open(path,mode='rb') as f: while True: data = f.read(1024) if not data: break if __name__ == '__main__': start = time.time() ps = [] for i in range(3): p = Process(target=task) p.start() ps.append(p) for p in ps: p.join() print(time.time()-start) 結果:0.3124856948852539 #多執行緒解決IO密集型 from threading import Thread import time a = 10 def task(): path =r'E:\python試學視訊\day27、28選課系統\11 測試程式2.mp4' with open(path,mode='rb') as f: while True: data = f.read(1024) if not data: break if __name__ == '__main__': start = time.time() ts = [] for i in range(3): t = Thread(target=task) t.start() ts.append(t) for t in ts: t.join() print(time.time()-start) 結果:0.1250016689300537
GIL是用於保護直譯器相關的資料,直譯器也是一段程式,肯定有其定義的各種資料
GIL並不能保證自己定義的資料的安全,所以當程式中出現多執行緒共享資料的時候就需要自定義加鎖.
三.執行緒池與程序池
什麼是程序池/執行緒池?
池表示是一個容器,本質就是一個儲存程序或執行緒的列表
IO密集型使用執行緒池,計算密集型使用程序池
為什麼需要執行緒池/程序池?
很多情況下需要控制程序或者執行緒在一個合理的範圍內,執行緒/程序池不僅幫我們控制執行緒/程序的數量,還幫我們完成了執行緒/程序的建立,銷燬,以及任務的分配
執行緒池的使用:
from concurrent.futures import ThreadPoolExecutor from threading import current_thread,active_count import time #建立執行緒池,指定最大執行緒數為3 如果不指定 預設為cpu核心數*5 pool = ThreadPoolExecutor(3) #不會立即開啟子執行緒 def task(): print('%s running..'%current_thread().name) print(active_count()) time.sleep(2) #提交任務到執行緒池 for i in range(10): pool.submit(task)
程序池的使用:
from concurrent.futures import ProcessPoolExecutor import time,os #建立程序池,最大程序數為3,預設為cpu個數 pool = ProcessPoolExecutor(3)#不會立即開啟子程序 def task(): print('%s running..'%os.getpid()) time.sleep(2) if __name__ == '__main__': #提交任務到程序池 for i in range(10): pool.submit(task) #第一次提交任務時會建立程序後續提交任務直接交給已經存在的程序來完成,如果沒有空閒程序就等待 結果: 1464 running.. 11732 running.. 8236 running.. 1464 running.. 11732 running.. 8236 running.. 1464 running.. 11732 running.. 8236 running.. 1464 running..
首先要明確,TCP是IO密集型,應該使用執行緒池
#多執行緒TCP伺服器 from concurrent.futures import ThreadPoolExecutor import socket server = socket.socket() server.bind(('192.168.12.207',4396)) server.listen() pool = ThreadPoolExecutor(3) #執行緒池,控制可以連線到伺服器的客戶端的個數 def task(client): while True: try: data = client.recv(1024) if not data: client.close() break client.send(data.upper()) except ConnectionResetError: client.close() break while True: client,addr = server.accept() t = pool.submit(task,client)
#多執行緒TCP客戶端 #使用多執行緒是為了可以一直輸入,不用等輸出了才可以輸入 from threading import Thread import socket client = socket.socket() client.connect(('192.168.12.207',4396)) def send_msg(): while True: msg = input('>>:').strip() if not msg: continue client.send(msg.encode('utf-8')) send_t = Thread(target=send_msg) send_t.start() while True: try: #這個也要自定義丟擲異常,如果伺服器終止,客戶端也會報錯 data = client.recv(1024) print(data.decode('utf-8')) except: client.close() break
訊號量也是一種鎖,適用於保證同一時間能有多少個程序或執行緒訪問
而執行緒池和程序池,沒有對資料訪問進行限制僅僅是控制數量
四.同步與非同步
同步(呼叫/執行/任務/提交),發起任務後必須等待任務結束,拿到一個結果才能繼續執行
非同步 發起任務後不需要關係任務的執行過程,可以繼續往下執行,但還是需要結果
非同步效率高於同步但是並不是所有任務都可以非同步執行,判斷一個任務是否可以非同步的條件是,任務發起方是否立即需要執行結果
同步不等於阻塞 非同步不等於非阻塞當使用非同步方式發起任務時 任務中可能包含io操作 非同步也可能阻塞同步提交任務 也會卡主程式 但是不等同阻塞,因為任務中可能在做一些計算任務,CPU沒有切換到其他程式
from concurrent.futures import ThreadPoolExecutor import time pool = ThreadPoolExecutor() def task(): time.sleep(1) print('sub thread run...') for i in range(10): pool.submit(task) #submit是以非同步的方式提交任務 print('over')
from concurrent.futures import ThreadPoolExecutor import time pool = ThreadPoolExecutor() def task(i): time.sleep(1) print('sub thread run ...') i += 1 return i for i in range(10): f = pool.submit(task,i) print(f) print(f.result()) #result是阻塞的,會等到這個任務執行完畢才能繼續執行,會將非同步變為同步 print('over')
#同步又變為了非同步 from concurrent.futures import ThreadPoolExecutor import time pool = ThreadPoolExecutor() def task(i): time.sleep(1) print('sub thread run ...') i += 1 return i fs = [] for i in range(10): f = pool.submit(task,i) fs.append(f) #是一個阻塞函式,會等到池子中的所有任務完成後繼續執行 pool.shutdown() #裡面有一個wait引數:預設值是True #注意:shutdown之後就不能提交新任務了 for i in fs: print(i.result()) print('over')