最近處理的大多數任務都是基于python的多執行緒實作的,然而使用python逃避不開的一個話題就是,python的GIL(的全稱是 Global Interpreter Lock)全域解釋器鎖是單執行緒的,那么是不是意味著python的多執行緒也是串行的?多執行緒對共享資源的使用就不需要鎖(執行緒鎖)了?
筆者一開始也是這么誤解:
既然python解釋器的鎖是單執行緒的===》那么經過解釋器生成的執行緒,是輪訓執行的(相當于單執行緒)===》然后推斷出執行緒訪問共享資源的時候不需要加執行緒鎖?
既然不需要加執行緒鎖,那么python為什么會存在執行緒鎖?這明顯是一個矛盾的問題,以上推論純屬YY,上述結論是不正確的,
其實上述推斷從第二步就開始錯誤了,其實某些時候,看似合理的推斷,是完成不成立的,
先做個小demo看一下效果,下面代碼很簡單,執行緒方法內部輪訓設定全域變數遞增,然后其多個執行緒呼叫該方法,執行緒方法在操作全域變數的時候暫時不適用執行緒鎖
#! /usr/bin/python # -*- coding: utf-8 -*- from threading import Thread,Lock g_var = 0 lock = Lock() def thread_tasks(): for i in range(100000): global g_var #lock.acquire() g_var = g_var+1 #lock.release() if __name__ == "__main__": t_list = [] for i in range(100): t = Thread(target=thread_tasks, args=[]) t_list.append(t) for t in t_list: t.setDaemon(True) t.start() for t in t_list: t.join() print('thread execute finish') print('global variable g_var is ' + str(g_var))
理論上出來的結果,最終全域變數的結果就是執行緒數*每個執行緒回圈的次數,這里執行緒數是100,執行緒方法內部回圈100000次,最終的結果“理論上”是10,000,000,
能動手的絕對不動嘴,實際執行下來發現并不是這樣的,甚至會出現各種意料之外的答案,原因何在?
這篇《也許你對 Python GIL 鎖的理解是 錯的》文章提到這個問題,理論上也比較簡單
然后再加上執行緒鎖,看看效果(需要注意的是:開啟執行緒鎖之后,不應該無腦開啟太多的執行緒,因為這個執行緒鎖會導致執行緒之間嚴重爭用問題)
關于python的多執行緒無法對CPU使用充分的問題,筆者是12核的機器,在跑上述代碼的時候,CPU使用情況如下,鑒于對python了解的不夠深入,不知道怎么解釋這種“python單位時間內只能使用同一個CPU”的問題


關于python多執行緒使用,根據筆者的測驗總結,也有一些思考
1,如果是遠程任務,就好比上述代碼thread_tasks中執行的是一個遠程服務(比如遠程資料庫訪問什么的),完全可以達到“并發”的效果,因為本地CPU僅負責啟動執行緒,執行緒在遠程服務端一樣可以做到并發效果,比如多執行緒壓測遠程訪問資料庫的時候,一樣可以打爆遠程資料庫服務器的CPU,
2,如果本地任務,且本地任務屬于非CPU密集型任務,比如IO操作,或者網路讀寫等等,多執行緒一樣可以達到并發效果,因為瓶頸并不在CPU使用上(整體效率不是增加CPU的使用就可以提升的)
3,如果本地任務,且本地任務屬于CPU密集型任務,此時python的多執行緒是無法發揮其功效的(多行程不香么,此時可以考慮多行程并發),
關于python的多行程并發模型,也非常簡單,相比執行緒鎖,行程之間的通訊,一個內置的queue就完事了,如下是模擬多行程并法實作上述計算,
from multiprocessing import Process, Pool, Queue from threading import Thread, Lock import time import progressbar import math g_var = 0 lock = Lock() def thread_tasks(q): for _ in range(100000): q.put(1) def consumer_q(q): global g_var p = progressbar.ProgressBar() p.start() while True: var = q.get(True) g_var = g_var + var try: p.update(int((g_var / (100 * 100000 - 1)) * 100)) except Exception as ex: pass p.finish() if __name__ == "__main__": q = Queue() p_list = [] p_consumer = Process(target=consumer_q, args=[q, ]) p_consumer.start() for i in range(50): p = Process(target=thread_tasks, args=[q, ]) p_list.append(p) for p in p_list: p.start() for p in p_list: p.join() time.sleep(1) print('\n sub_process execute finish') p_consumer.terminate()
這里非常客觀地評價了python的多行程和多執行緒并發對比:https://www.cnblogs.com/massquantity/p/10357898.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/224924.html
標籤:Python
