一、多線程與多進程的效率對比 (1)多線程 from threading import Thread,current_thread # 多線程 current_thread 當前線程 import time def run(msg): print("這個是獨立線程運行的代碼",msg) time.sleep(1) if __name__ == '__main__': new_time = time.time() print("++++++++++ main start +++++++++++") for x in range(10): t1 = Thread(target=run,args=("這個是參數%s"%x,)) t1.start() print("========== main end ==========", current_thread().getName(),time.time()-new_time) # current_thread().getName()可以得到當前主線程的名稱 ''' 運行結果: ++++++++++ main start +++++++++++ 這個是獨立線程運行的代碼 這個是參數0 這個是獨立線程運行的代碼 這個是參數1 這個是獨立線程運行的代碼 這個是參數2 這個是獨立線程運行的代碼 這個是參數3 這個是獨立線程運行的代碼 這個是參數4 這個是獨立線程運行的代碼 這個是參數5 這個是獨立線程運行的代碼 這個是參數6 這個是獨立線程運行的代碼 這個是參數7 這個是獨立線程運行的代碼 這個是參數8 這個是獨立線程運行的代碼 這個是參數9 ========== main end ========== MainThread,0.0010006427764892578 ''' (2)多進程 from multiprocessing import Process # 多進程 import time def run(msg): print("這個是獨立線程運行的代碼",msg) time.sleep(1) if __name__ == '__main__': new_time = time.time() print("++++++++++ main start +++++++++++") for x in range(10): t1 = Process(target=run,args=("這個是參數%s"%x,)) t1.start() print("========== main end ==========",time.time()-new_time) ''' 運行結果: ++++++++++ main start +++++++++++ ========== main end ========== 0.2971487045288086 這個是獨立線程運行的代碼 這個是參數0 這個是獨立線程運行的代碼 這個是參數2 這個是獨立線程運行的代碼 這個是參數1 這個是獨立線程運行的代碼 這個是參數9 這個是獨立線程運行的代碼 這個是參數3 這個是獨立線程運行的代碼 這個是參數5 這個是獨立線程運行的代碼 這個是參數8 這個是獨立線程運行的代碼 這個是參數4 這個是獨立線程運行的代碼 這個是參數7 這個是獨立線程運行的代碼 這個是參數6 ''' 二、多線程,類的實現方式
from threading import Thread class Team(Thread): def __init__(self, name): super().__init__(name=name) # 繼承自Thread def run(self): for i in range(3): print("這是類中的子線程") if __name__ == '__main__': print("+++++++++++ main start ++++++++++++") t = Team("麗麗") t.start() print("========== main end ============") ''' 運行結果: +++++++++++ main start ++++++++++++ 這是類中的子線程 # 可以看出多線程的運行速度幾乎超過了主線程,這在多進程中是不會出現的 ========== main end ============ 這是類中的子線程 這是類中的子線程 這是類中的子線程 ''' 三、多線程共享全局變量
我們前面已經說過了,多進程之間的數據時獨立的,各自都有一份,即便是全局變量也不共享,那麼一個進程中的多線程之間的全局變量呢?注意:多線程之間的全局數據數是共享的,因爲多線程是在一個進程中,數據是互相可以訪問的,案例如下:
import threading import time num = 100 def task1(): global num for x in range(3): num += 1 print("run1中的num=",num) def task2(): print("run2中的num=",num) def run(): t1 = threading.Thread(target=task1) t1.start() time.sleep(1) t2 = threading.Thread(target=task2) t2.start() if __name__ == '__main__': run() |
由此可知,一個進程間的多線程(下面開始統一叫多線程,因爲我們一直說的多線程就是一個進程下的多線程)的全局數據是共享的。
四、互斥鎖(metux)當多個線程⼏乎同時修改某⼀個共享數據的時候,需要進⾏同步控制線程同步能夠保證多個線程安全訪問競爭資源,最簡單的同步機制是引⼊互斥鎖。
互斥鎖爲資源引⼊⼀個狀態:鎖定/⾮鎖定。
某個線程要更改共享數據時,先將其鎖定,此時資源的狀態爲“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“⾮鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有⼀個線程進⾏寫⼊操作,從⽽保證了多線程情況下數據的正確性。
from threading import * num = 0 def run1(lock): global num for x in range(1000000): lock.acquire( ) # 將程序鎖定 num += 1 lock.release( ) # 解鎖 print(num) def run2(lock): global num for x in range(1000000): lock.acquire() # 將程序鎖定 num += 1 lock.release() # 解鎖 print(num) if __name__ == '__main__': lock = Lock() # 創建互斥鎖,用於解決非線程安全問題 t1 = Thread(target=run1,args=(lock,)) t1.start() t2 = Thread(target=run2,args=(lock,)) t2.start() # 運行結果:1993755 # 2000000如果不加互斥鎖,運行的結果應該會小於2000000,可參考12.系統編程(多進程和多線程)
五、同步(藉助於互斥鎖實現的)
from threading import Thread,Lock import time myLock1 = Lock() myLock2 = Lock() myLock3 = Lock() # 通過加鎖來實現一個同步案例,同步即多線程中的協同步調 def run1(): while True: if myLock1.acquire(): print("run1") myLock2.release() time.sleep(1) def run2(): while True: if myLock2.acquire(): print("run2") myLock3.release() time.sleep(1) def run3(): while True: if myLock3.acquire(): print("run3") myLock1.release() time.sleep(1) if __name__ == '__main__': myLock2.acquire() myLock3.acquire() t1 = Thread(target=run1) t1.start() t3 = Thread(target=run3) t3.start() t2 = Thread(target=run2) t2.start() # 運行結果: # run1 #run2 # run3 # run1 #run2 #run3 ......
六、死鎖
在線程間共享多個資源的時候,如果兩個線程分別佔有⼀部分資源並且同時等待對⽅的資源,就會造成死鎖。
儘管死鎖很少發⽣,但⼀旦發⽣就會造成應⽤的停⽌響應。
from threading import Thread,Lock import time # 申請兩個全局鎖 myLock1= Lock() myLock2= Lock() def run1(): print("第一個子線程運行 ") if myLock1.acquire(timeout=2): # 等待2s print("lock1已經加鎖了") time.sleep(1) # 人爲的代碼停止一下 if myLock2.acquire(): # 返回布爾值,表示加鎖成功,第一次返回布爾值,第二次卡死 print("第一個執行了嗎?") myLock2.release() myLock1.release() def run2(): print("第二個子線程運行了") if myLock2.acquire(): print("lock2已經加鎖了") time.sleep(1) if myLock1.acquire(): print("第二個執行了嗎?") myLock1.release() myLock2.release() if __name__ == '__main__': t1 = Thread(target=run1) t1.start() t2 = Thread(target=run2) t2.start() # 運行結果: # 第一個子線程運行 # lock1已經加鎖了 # 第二個子線程運行了 # lock2已經加鎖了 可以用遞歸加鎖的方式解決死鎖現象:rlock(),或者銀行家算法
七、隊列
八、Threadlocal
在多線程環境下,每個線程都有⾃⼰的數據。⼀個線程使⽤⾃⼰的局部變量⽐使⽤全局變量好,
因爲局部變量只有線程⾃⼰能看見,不會影響其他線程,⽽全局變量的修改必須加鎖。
from threading import Thread, local mylocal = local() # 先new一個local對象 def printMsg(): print(mylocal.name, mylocal.age, mylocal.gender) def speak(): print("%s說了一句話" % mylocal.name) def run1(): # 自身函數中需要的參數綁定到本地線程Threadlocal上 mylocal.name = "張三" mylocal.age = 18 mylocal.gender = "女" printMsg() speak() def run2(): mylocal.name = "李四" mylocal.age = 25 mylocal.gender = "男" printMsg() speak() if __name__ == '__main__': t1 = Thread(target=run1) t2 = Thread(target=run2) t1.start() t2.start() # 運行結果: 張三 18 女 # 張三說了一句話 # 李四 25 男 # 李四說了一句話