併發之多執行緒
概念:執行緒是應用程式中工作的最小單元,或者又稱之為微程序。
組成:它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務。
闡釋:執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。執行緒可以共享(呼叫)程序的資料資源
優點:共享記憶體,IO操作時候,創造併發操作
缺點:%^&%%^&$$(學了你就知道了,CPython個**的!)
關於多執行緒
多執行緒類似於同時執行多個不同程式,多執行緒執行有如下優點:
- 使用執行緒可以把佔據長時間的程式中的任務放到後臺去處理。
- 使用者介面可以更加吸引人,這樣比如使用者點選了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度
- 程式的執行速度可能加快
- 在一些等待的任務實現上如使用者輸入、檔案讀寫和網路收發資料等,執行緒就比較有用了。在這種情況下我們可以釋放一些珍貴的資源如記憶體佔用等等。
執行緒在執行過程中與程序還是有區別的。每個獨立的執行緒有一個程式執行的入口、順序執行序列和程式的出口。但是執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。
每個執行緒都有他自己的一組CPU暫存器,稱為執行緒的上下文,該上下文反映了執行緒上次執行該執行緒的CPU暫存器的狀態。
指令指標和堆疊指標暫存器是執行緒上下文中兩個最重要的暫存器,執行緒總是在程序得到上下文中執行的,這些地址都用於標誌擁有執行緒的程序地址空間中的記憶體。
- 執行緒可以被搶佔(中斷)。
- 在其他執行緒正在執行時,執行緒可以暫時擱置(也稱為睡眠) -- 這就是執行緒的退讓。
執行緒可以分為:
- 核心執行緒:由作業系統核心建立和撤銷。
- 使用者執行緒:不需要核心支援而在使用者程式中實現的執行緒。
使用:
- threading(推薦使用)
thread 模組已被廢棄。使用者可以使用 threading 模組代替。所以,在 Python3 中不能再使用"thread" 模組。為了相容性,Python3 將 thread 重新命名為 "_thread"。反正你不要再用就是了! 我習慣上用 from threading import Thread
Python中使用執行緒有兩種方式:函式或者用類來包裝執行緒物件。(跟程序差不多的)
Python3 通過兩個標準庫 _thread 和 threading 提供對執行緒的支援。
_thread 提供了低級別的、原始的執行緒以及一個簡單的鎖,它相比於 threading 模組的功能還是比較有限的。
threading 模組除了包含 _thread 模組中的所有方法外,還提供的其他方法:
- threading.currentThread(): 返回當前的執行緒變數。
- threading.enumerate(): 返回一個包含正在執行的執行緒的list。正在執行指執行緒啟動後、結束前,不包括啟動前和終止後的執行緒。
- threading.activeCount(): 返回正在執行的執行緒數量,與len(threading.enumerate())有相同的結果。
除了使用方法外,執行緒模組同樣提供了Thread類來處理執行緒,Thread類提供了以下方法:
- run(): 用以表示執行緒活動的方法。
- start():啟動執行緒活動。
- join([time]): 等待至執行緒中止。這阻塞呼叫執行緒直至執行緒的join() 方法被呼叫中止-正常退出或者丟擲未處理的異常-或者是可選的超時發生。
- setDaemon(True):守護主執行緒,跟隨主執行緒退(必須要放在start()上方)
- isAlive(): 返回執行緒是否活動的。
- getName(): 返回執行緒名。
- setName(): 設定執行緒名。
定義執行緒
import threading # 執行緒模組 import time # 建立執行緒 def onepiece1(n): print("路飛正在使用橡膠火箭炮%s,攻擊力%s" %(time.ctime(),n)) time.sleep(3) print("路飛結束該技能%s" %time.ctime()) def onepiece2(n): print("艾尼路正在出雷神萬擊%s你,攻擊力%s" %(time.ctime(),n)) time.sleep(5) print("艾尼路結束該技能%s" %time.ctime()) if __name__ == '__main__': thread_1 = threading.Thread(target=onepiece1,args=(10,)) # 建立子執行緒 thread_2 = threading.Thread(target=onepiece2,args=(9,)) thread_1.start() # pyhton1.join() thread_2.start() thread_2.join() # 等待執行緒終止 print("ending Fighting")函式式
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self): # 定義每個執行緒要執行的函式 print("running on number:%s" %self.num) time.sleep(3) print("ending......") if __name__ == '__main__': t1 = MyThread(1) # 繼承這個類,把1這個引數,傳給num ,t1就是個執行緒物件 t2 = MyThread(2) t1.start() t2.start()繼承類
GIL
GIL 的存在是當初為了解決單核下多執行緒資料讀寫不安全的問題(主要保護直譯器裡的資料) 於是給每個程序加了互斥量 類似lock 但實現原理不一樣
lock是為了保護我們自己寫的程式用到的資料
但這導致後來出現多核cpu時,python不能在多核的時候實現真正的並行,,這些執行緒只會優先在一個核裡工作
這是屬於直譯器的弊病,可以解決但牽涉太多,成本太高了,而更換直譯器許多庫都不能使用,所以只能儘量避開這個影響:
繞過GIL的影響的辦法是:
計算密集的任務 使用多程序 io密集的 使用多執行緒,因為在io時,GIL會被釋放!
參考文章 https://blog.csdn.net/nawenqiang/article/details/79731858
執行緒互斥
多個執行緒共同對某個資料修改,則可能出現不可預料的結果,為了保證資料的正確性,需要對多個執行緒進行同步。
使用 Thread 物件的 Lock 和 Rlock 可以實現簡單的執行緒互斥。
這兩個物件都有 acquire 方法和 release 方法。
對於那些需要每次只允許一個執行緒操作的資料,可以將其操作放到 acquire 和 release 方法之間。
def sub(): global num thread_lock_A.acquire() # 用於執行緒同步 tmep = num time.sleep(0.001) num = tmep - 1 thread_lock_A.release() # 釋放,開啟下一個執行緒 # 問題,lock之後100個執行緒就變為了序列執行,鎖內的程式碼 li = [] for i in range(100): t = threading.Thread(target=sub) t.start() li.append(t) for t in li: t.join() print("ending") print(num)執行緒互斥