一、概念
1.什么是進程
計算機程序只不過是磁盤中可執行的二進制(或其他類型)的數據。它們只有在被讀取到內存中,被操作系統調用的時候才開始它們的生命期。
進程(有時被稱為重量級進程)是程序的一次執行。每個進程都有自己的地址空間、內存、數據棧及其它記錄其運行軌跡的輔助數據。
操作系統管理在其上運行的所有進程,並為這些進程公平的分配時間,進程也可以通過fork和spawn操作來完成其它的任務。
不過各個進程有自己的內存空間、數據棧等,所以只能使用進程間通訊,而不能直接共享信息。
2.什么是線程
線程(有時候被稱為輕量級進程)跟進程有些相似,不同的是,所有的線程運行在同一個進程中,共享相同的運行環境。
線程有開始,順序執行和結束三部分。它有一個自己的指令指針,記錄自己運行到什么地方。
線程的運行可能被搶占(中斷),或暫時的被掛起(也叫睡眠),讓其他的線程運行,這叫做讓步。
一個進程中的各個線程之間共享同一片數據空間,所以線程之間可以比進程之間更方便地共享數據以及相互通訊。
線程一般都是並發執行的,正是由於這種並行和數據共享的機制使得多個任務的合作變為可能。
實際上,在單CPU的系統中,真正的並發是不可能的,每個線程會被安排成每次只運行一小會,然后就把CPU讓出來,讓其他的線程去運行。
每個線程都只做自己的事,在需要的時候跟其它的線程共享運行的結果。
當然,這樣的共享並不是完全沒有危險的。如果多個線程共享訪問同一片數據,則由於數據訪問的順序不一樣,有可能導致數據結果的不一致問題。這叫做競態條件。
幸運的是,大多數線程庫都帶有一系列的同步原語,來控制線程的執行和數據的訪問。
3.進程與線程的關系
一個線程可以創建和撤銷另一個線程,同一個進程中的多個線程之間可以並發執行。
相對進程而言,線程是一個更加接近於執行體的概念,它可以與同進程中的其它線程共享數據,但擁有自己的棧空間,擁有獨立的執行序列。
進程和線程的主要區別在於它們是不同的操作系統資源管理方式。
進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。
線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,
所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大。
但對於一些要求同時進行並且又要共享某些變量的並發操作,只能使用線程,不能用進程。
(1)簡而言之,一個程序至少有一個進程,一個進程至少有一個線程。
(2)線程的划分尺度小於進程,使得多線程程序的並發性高。
(3)另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,而極大的提高了程序的運行效率。
(4)線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。
但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
(5)從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。
但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的主要區別。
線程和線程在使用上各有優缺點:線程執行開銷小,但不利於資源的管理和保護;而進程正好相反。線程適合於在SMP機器上運行,而進程則可以跨機器遷移。
4.並行與並發
並行:兩個或多個事件在同一時刻發生;
並發:兩個或多個事件在同一時間間隔內發生。
在操作系統中個,並發是指一個時間段中有幾個程序都處於已啟動到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行。
在網絡服務器上,並發是指同一時刻能處理的連接數。比如服務器建立了100個TCP連接,即服務器同時維護了100個socket,那么並發量就是1000。
並發的關鍵是有處理多個任務的能力,不一定要同時。
並行的關鍵是有同時處理多個任務的能力。
5.同步和異步
同步:發送方發送數據后,等待接收方發回響應以后才發下一個數據包的通訊方式。步驟一致。
異步:發送方發送數據后,不等待接收方發回響應,接着發送下一個數據包的通訊方式。
同步是阻塞模式,異步是非阻塞模式。
二、鎖
1.lock
進程同步鎖
我們知道進程一般都是並發執行,有時候並發執行特別混亂,必須讓進程串行,雖然可以使用join,但是join不太靈活,這里就會使用到同步鎖。

#進程同步鎖 from multiprocessing import Process,Lock import os,time,random def work(lock): #上同一把鎖 lock.acquire() print('%s is running'% os.getpid()) time.sleep(random.randint(1,3)) print("%s is done"% os.getpid()) lock.release() if __name__ == "__main__": lock = Lock() #和join一樣,一把鎖 #建一把鎖,將其放在函數之中,每個進程都會調用 p1 = Process(target=work,args=(lock,)) p2 = Process(target=work,args=(lock,)) p3 = Process(target=work,args=(lock,)) p1.start() p2.start() p3.start() #執行結果: 4292 is running #一個進程執行完畢之后再執行下一個 4292 is done 11748 is running 11748 is done 6184 is running 6184 is done
線程互斥鎖
我們知道線程是共享一個進程的地址空間的,改變地址空間中的環境變量就會改變所有線程的環境變量,但是如果我們想用線程來更改環境變量,必須使用互斥鎖。

#線程互斥鎖 from threading import Thread,Lock import time n = 100 def task(): global n with lock: temp = n time.sleep(0.1) n = temp-1 if __name__ == '__main__': lock = Lock() lst = [] for i in range(50): t = Thread(target=task) lst.append(t) #創建一個線程 t.start() #啟動一個線程 for t in lst: t.join() print(n)
這里可以用一個例子來說明Lock和join的關系
如果使用join,那么就只有幾個人能買到票,前面的沒買到,后面的查票都不行,這個添加到查詢上面的的鎖不合適

from multiprocessing import Process import json,random,time,os def search(): #查票 with open("db.txt",encoding='utf-8') as f: dic=json.load(f) print("%s 剩余票數 %s" %(os.getpid(),dic['count'])) def get(): #搶票 with open("db.txt",encoding='utf-8') as read_f: dic = json.load(read_f) if dic['count'] > 0: dic['count'] -=1 time.sleep(random.randint(1,3)) #模擬手速+網速 with open("db.txt",'w', encoding='utf-8') as write_f: json.dump(dic,write_f) print("\033[43;1m%s搶票成功\033[0m "%os.getpid()) def task(): search() get() if __name__ == "__main__": for i in range(10): p = Process(target=task) p.start() p.join() #使用join永遠只有第一個人能夠搶到 #執行結果: 2408 剩余票數 2 2408搶票成功 11328 剩余票數 1 11328搶票成功 12364 剩余票數 0 16460 剩余票數 0 11848 剩余票數 0 11820 剩余票數 0 14792 剩余票數 0 14084 剩余票數 0 5276 剩余票數 0 6160 剩余票數 0
使用Lock就會相對好一些

from multiprocessing import Process,Lock import json,random,time,os def search(): #查票 with open("db.txt",encoding='utf-8') as f: dic=json.load(f) print("%s 剩余票數 %s" %(os.getpid(),dic['count'])) def get(): #搶票 with open("db.txt",encoding='utf-8') as read_f: dic = json.load(read_f) if dic['count'] > 0: dic['count'] -=1 time.sleep(random.randint(1,3)) #模擬手速+網速 with open("db.txt",'w', encoding='utf-8') as write_f: json.dump(dic,write_f) print("\033[43;1m%s搶票成功\033[0m "%os.getpid()) def task(lock): search() #查找可能是並發執行, lock.acquire() #搶票才要鎖,這也是用join的另一個不同 get() lock.release() if __name__ == "__main__": lock = Lock() for i in range(10): p = Process(target=task,args=(lock,)) p.start() #鎖和join都是把並發變為串行,但是鎖比join靈活,lock能讓局部串行,而lock只能讓全局串行 #執行結果 13232 剩余票數 3 14856 剩余票數 3 12968 剩余票數 3 15992 剩余票數 3 7880 剩余票數 3 10200 剩余票數 3 13100 剩余票數 3 7772 剩余票數 3 14252 剩余票數 3 352 剩余票數 3 2680 剩余票數 3 2116 剩余票數 3 17380 剩余票數 3 4476 剩余票數 3 12396 剩余票數 3 15932 剩余票數 3 13232搶票成功 12368 剩余票數 2 10916 剩余票數 2 9156 剩余票數 2 9864 剩余票數 2 14856搶票成功 12968搶票成功
可以看出Lock和join雖然在功能上類似,但是復雜情況,Lock機制還是更靈活
2.GIL
python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制。
python在設計之初就考慮要在主循環中,同時只有一個線程執行,就像單CPU的系統中運行多個進程那樣,內存中可以存放多個程序,但任意時刻,只有一個程序在CPU執行。
同樣的,雖然Python解釋器中可以“運行”多個線程,但在任意時刻,只有一個線程在解釋器中運行。
對python虛擬機的訪問由全局解釋器鎖來控制,正是這個鎖能保證同一時刻只有一個線程在執行。
在多線程環境中,python虛擬機按一下方式執行。
(1)設置GIL
(2)切換到一個線程去運行
(3)運行:a.指定數量的字節碼的指令,或者; b.線程主動讓出控制
(4)把線程設置為睡眠狀態
(5)解鎖GIL
(6)再次重復以上所有步驟
在調用外部代碼的時候,GIL將會被鎖定,直到這個函數結束為止。
當一個線程結束計算,它就退出了。線程可以調用thread.exit()之類的退出函數,也可以使用Python退出進程的標准方法。
GIL本質就是一把互斥鎖,既然是互斥鎖,所有互斥鎖的本質都一樣,都是將並發運行變成串行,以此來控制同一時間內共享數據只能被一個任務所修改,進而保證數據安全。
可以肯定的一點是:保護不同的數據的安全,就應該加不同的鎖。

from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(100000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count()) #本機為4核 start=time.time() for i in range(4): p=Process(target=work) #耗時5s多 p=Thread(target=work) #耗時18s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))

from multiprocessing import Process from threading import Thread import threading import os,time def work(): time.sleep(2) print('===>') if __name__ == '__main__': l=[] print(os.cpu_count()) #本機為4核 start=time.time() for i in range(400): # p=Process(target=work) #耗時12s多,大部分時間耗費在創建進程上 p=Thread(target=work) #耗時2s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
三、線程和進程的開啟方式
開啟進程有兩種方式:

#第一種方式: from multiprocessing import Process import time,random def task(name): print('%s is running'%name) time.sleep(random.randint(1,5)) print('%s is ku'%name) if __name__ == "__main__": p1 = Process(target=task,args=('kebi',)) p1.start() #執行我i先后 #第二種方式(很少使用) from multiprocessing import Process import time class Myclass(Process): def __init__(self,name): super().__init__() self.name = name def run(self): time.sleep(2) print('%s is running '% self.name) if __name__ == '__main__': p = Myclass('cx') p.start() #開啟進程的第二種方式,之前給Procee傳參,現在是使用繼承
同樣的,開啟線程也有兩種方式

#第一種方式 from threading import Thread import time,os def task(): print('%s is running%s'% (os.getpid(),os.getppid())) time.sleep(2) print('%s is done'%os.getpid()) if __name__ =="__main__": t = Thread(target=task) t.start() print("主",os.getpid()) #第二種方式(使用的少) from threading import Thread import time,os class Mythread(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s is running' % os.getpid()) time.sleep(2) print('%s is done' % os.getpid()) if __name__ == "__main__": t = Mythread('kebi') t.start()
四、進程和線程的名稱空間
進程的名稱空間:進程有獨立的地址空間,所以各個進程之間不會相互影響。
線程的名稱空間:同一個進程中的線程共享一個名稱空間。

#進程 from multiprocessing import Process def task(): global n n=0 n = 100 if __name__ =="__main__": p = Process(target=task) p.start() print(n) #執行結果 100

#線程 from threading import Thread def task(): global n n=0 n = 100 if __name__ =="__main__": t = Thread(target=task) t.start() print(n) #執行結果 0
五、進程池和線程池
進程池內部維護一個進程序列,當使用時,則去進程池中的獲取一個進程,如果進程池序列中沒有可供使用的進程,那么進程就會等待,直到進程池中有可用進程為止,線程池也是類似。
進程池:

from concurrent.futures import ProcessPoolExecutor import os,random def func(name): print("%s吃了又一碗飯:%s" %(name,os.getpid())) time.sleep(random.randint(1, 3)) if __name__ == "__main__": p = ProcessPoolExecutor(3) #創建一個進程池,里面容納3個進程 for i in range(7): obj = p.submit(func,'科比%i'%i) p.shutdown(wait=True) #類似與join,並且可以關門,以防在等的過程中又提交新的任務 print("主進程") #執行結果 科比0吃了又一碗飯:13980 科比1吃了又一碗飯:9636 科比2吃了又一碗飯:12660 科比3吃了又一碗飯:13980 科比4吃了又一碗飯:12660 科比5吃了又一碗飯:9636 科比6吃了又一碗飯:13980 主進程
線程池:

from threading import Thread,current_thread from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import time,os def task(n): print('%s is running'% current_thread().getName()) time.sleep(3) return n**2 if __name__ =="__main__": t = ThreadPoolExecutor(3) #默認是CPU的核數*5 objs = [] for i in range(7): obj = t.submit(task,i) objs.append(obj) t.shutdown(wait=True) #異步,最終打印結果 for obj in objs: print(obj.result()) print("主",current_thread().getName()) #執行結果 ThreadPoolExecutor-0_0 is running ThreadPoolExecutor-0_1 is running ThreadPoolExecutor-0_2 is running ThreadPoolExecutor-0_2 is running ThreadPoolExecutor-0_1 is running ThreadPoolExecutor-0_0 is running 0 1 4 ThreadPoolExecutor-0_1 is running 9 16 25 36 主 MainThread
六、守護進程和守護線程
無論是進程還是線程,當主進程結束的時候,守護進程也會隨之結束。
對於進程來說,所謂的主進程結束就是不包括子進程;對於線程來說,所謂的主進程結束是包括其中線程都結束。
守護進程:守護進程必須在子線程開啟之前設置,而且守護進程不能有子進程。

#守護進程 from multiprocessing import Process import os,time,random def task(): print('%s is running '%os.getpid()) time.sleep(2) print('%s is done' % os.getppid()) if __name__ == "__main__": p = Process(target=task) p.daemon = True #1.必須在進程開啟之前 2.不能再開啟子進程 p.start() print("主") #執行結果 主 #子進程還沒開始,主進程就已經結束了 變一變條件也許會更加明了 #讓主進程再多執行幾秒,守護進程就可以執行部分代碼 from multiprocessing import Process import os,time,random def task(): print('%s is running '%os.getpid()) time.sleep(2) print('%s is done' % os.getppid()) if __name__ == "__main__": p = Process(target=task) p.daemon = True #1.必須在進程開啟之前 2.不能再開啟子進程 p.start() time.sleep(2) print("主") #執行結果 3760 is running #有新的東西了 主
守護線程:設置守護線程必須在子線程開始之前,必須是所有子線程都執行完,,主進程才會結束

from threading import Thread import os,time,random def task(): print('%s is running '%os.getpid()) time.sleep(2) print('%s is done' % os.getppid()) if __name__ == "__main__": p = Thread(target=task) p.daemon = True #主線程結束,殺子線程 p.start() #守護線程里面可以再開子線程 print("主") #所有的非守護線程都死了守護進程才會死,次出之所以能打印,主線程執行需要時間 #執行結果 14980 is running #守護線程並沒有執行完畢 主 現在我添加一個子線程 from threading import Thread import os,time,random def task(): print('%s is running '%os.getpid()) time.sleep(2) print('%s is done' % os.getppid()) def mask(): print("%s start...."% os.getpid()) time.sleep(3) print("%s over...."% os.getpid()) if __name__ == "__main__": p = Thread(target=task) p1 = Thread(target=mask) p.daemon = True #主線程結束,殺子線程 p.start() #守護線程里面可以再開子線程 p1.start() print("主") #所有的非守護線程都死了守護進程才會死,次出之所以能打印,主線程執行需要時間 #執行結果 15896 is running 15896 start.... 主 14716 is done 15896 over.... #由於子進程的執行時間非常長,因此守護線程也能全部執行完 與守護進程不同的是,守護線程可以有子進程 from threading import Thread import os,time,random def task(): c = Thread(target=mask) c.start() print('%s is running '%os.getpid()) time.sleep(2) print('%s is done' % os.getppid()) def mask(): print("守護線程中的子線程") if __name__ == "__main__": p = Thread(target=task) p.daemon = True p.start() #守護線程里面可以再開子線程 time.sleep(3) #需要主線程的執行時間稍微長一點 print("主")
七、進程隊列和線程隊列
進程彼此之間互相隔離,要實現進程間通信(IPC),multiprocess模塊支持兩種形式:隊列和管道。
這兩種方式都是使用消息傳遞的。隊列是基於管道和鎖來實現。
進程隊列

from multiprocessing import Queue q = Queue() #默認依托內存大小,不要傳太大的東西 q.put({"name":"kebi"})#可以放任意類型的數據 q.put((1,2,3)) q.put('everything is possible') print(q.get()) print(q.get()) print(q.get()) print(q.get()) #執行結果 {'name': 'kebi'} (1, 2, 3) everything is possible
線程隊列
用法基本與進程隊列一樣
有幾種模式常用:先進先出、后進先出、優先級。

import queue # q = queue.LifoQueue() q = queue.Queue() #先進先出 q.put("one")#可以放任意類型的數據 q.put("two") q.put("three") print(q.get()) print(q.get()) print(q.get()) #執行結果 one two three

#棧 import queue q = queue.LifoQueue() q.put("one")#可以放任意類型的數據 q.put("two") q.put("three") print(q.get()) print(q.get()) print(q.get()) #執行結果 three two one

#優先級 import queue q = queue.PriorityQueue() q.put((10,'one')) q.put((-1,'two')) q.put((5,'three')) print(q.get()) print(q.get()) print(q.get()) #執行結果 (-1, 'two') (5, 'three') (10, 'one')
八、信號量
信號量主要是用來維持有限的資源,使得在一定的時間內使用該資源的線程進程只是指定的數量
信號量與進程池是完全不同的概念,信號量是多個進程共用有限資源,而進程池多個任務用指定個進程來實現。
進程池

#三個人做10個工作 from concurrent.futures import ProcessPoolExecutor import os,time,random def job(name): print("%start.....%s"% (name,os.getpid())) time.sleep(1) # print("%sover.....%s"% (name,os.getpid())) names = ['kebi','maoxian','xiaoniao'] if __name__ == "__main__": p = ProcessPoolExecutor(3) for i in range(10): name = names[random.randint(0, 2)] obj=p.submit(job,name) obj.result() p.shutdown(wait=True) #執行結果 xiaoniaotart.....4156 maoxiantart.....5544 kebitart.....3084 xiaoniaotart.....4156 maoxiantart.....5544 kebitart.....3084 maoxiantart.....4156 xiaoniaotart.....5544 kebitart.....3084 kebitart.....4156

#10個人要上廁所,廁所只有三個 from multiprocessing import Process,Semaphore import time,random,os def task(num): print("%s 上廁所%d"%(os.getpid(),num)) time.sleep(random.randint(1,3)) if __name__ == "__main__": sm = Semaphore(3) for i in range(10): num = random.randint(1,3) p = Process(target=task,args=(num,)) p.start() #執行結果 8544 上廁所3 7868 上廁所2 5888 上廁所2 5736 上廁所2 10440 上廁所2 15564 上廁所1 16320 上廁所1 14360 上廁所2 1824 上廁所2 6528 上廁所3
九、生產者消費者模型
1.為什么要使用生產着消費者模型
在線程世界里,生產者就是生產數據的線程,消費者就是消費消費數據的線程。
在多進程開發當中,如果生產者處理很快,消費者處理很慢,那么生產者就必須等待消費者處理完,才能繼續生產數據,
同樣的道理,如果消費者的處理能力大於生產者。為了解決生產者和消費者強耦合造成的時間浪費問題,引入了生產者和消費者模型
2.什么是生產者消費者模型
生產者消費者模型是通過一個容器來解決生產者和消費者的強耦合問題,
生產者和消費者彼此之間不能直接通訊,而通過阻塞隊列來通訊,所以生產者生產完數據之后不用等待消費者處理,直到仍給阻塞隊列,
消費者不找生產者要數據,而是直接從阻塞隊列取值,阻塞隊列就相當於一個緩沖區,平衡了生產者和消費者的處理能力
3.實例

import time,os,random from multiprocessing import Queue,Process def procducer(q): #定義一個生產者函數 for i in range(1,11): res = "第%s個包子" %i time.sleep(0.5) q.put(res) #往框里面放一個包子 print('%s 生產了 %s'%(os.getpid(),res)) def consumer(q): #定義一個消費者函數 while True: res = q.get() if res is None: break print('\033[43;1m%s 吃了%s\033[0m'%(os.getpid(),res)) time.sleep(random.randint(1,2)) if __name__ == '__main__': q = Queue() #隊列 p = Process(target=procducer,args=(q,)) #進程 c = Process(target=consumer,args=(q,)) # p.start() c.start() p.join() #程序停不下來,主進程沒死,消費者沒死,通過判斷是否為空來判斷是否結束不靠譜 q.put(None) #什么時候算生產完,怎么確定生產完了,p.join肯定完了 #放一個None進去 #隊列可以實現生產者和消費者之間的解耦 #執行結果 8920 生產了 第1個包子 18304 吃了第1個包子 8920 生產了 第2個包子 8920 生產了 第3個包子 8920 生產了 第4個包子 18304 吃了第2個包子 8920 生產了 第5個包子 8920 生產了 第6個包子 8920 生產了 第7個包子 8920 生產了 第8個包子 18304 吃了第3個包子 8920 生產了 第9個包子 8920 生產了 第10個包子 18304 吃了第4個包子 18304 吃了第5個包子 18304 吃了第6個包子 18304 吃了第7個包子 18304 吃了第8個包子 18304 吃了第9個包子 18304 吃了第10個包子

import time,os,random from multiprocessing import Queue,Process,JoinableQueue def procducer(food,q): #定義一個生產者函數 for i in range(1,4): res = "%s%s" %(food,i) time.sleep(0.5) q.put(res) #往框里面放一個包子 print('%s 生產了 %s'%(os.getpid(),res)) q.join() #不生產了,等東西被取完,等消費者發消息 def consumer(q): #定義一個消費者函數 while True: res = q.get() # if res is None: # break print('\033[43;1m%s 吃了%s\033[0m'%(os.getpid(),res)) time.sleep(random.randint(1,2)) q.task_done() #發信號的操作 if __name__ == '__main__': q = JoinableQueue() p1 = Process(target=procducer,args=('包子',q,)) p2 = Process(target=procducer,args=('玉米',q,)) p3 = Process(target=procducer,args=('狗糧',q,)) c1 = Process(target=consumer,args=(q,)) c2 = Process(target=consumer,args=(q,)) c1.daemon=True c2.daemon=True p1.start() p2.start() p3.start() c1.start() c2.start() p1.join() p2.join() p3.join() #生產者結束,——》q.join()——》消費者確實把所有數據都收到 #主進程結束之后,守護線程隨之結束,這樣消費者進程就不會存活
十、死鎖現象與遞歸鎖
進程也有死鎖與遞歸鎖,所謂死鎖,是指兩個或兩個以上的進程或線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些在互相等待的進程稱為死鎖進程,如下就是死鎖

from threading import Thread,Lock import time mutexA=Lock() mutexB=Lock() class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('\033[41m%s 拿到A鎖\033[0m' %self.name) mutexB.acquire() print('\033[42m%s 拿到B鎖\033[0m' %self.name) mutexB.release() mutexA.release() def func2(self): mutexB.acquire() print('\033[43m%s 拿到B鎖\033[0m' %self.name) time.sleep(2) mutexA.acquire() print('\033[44m%s 拿到A鎖\033[0m' %self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): t=MyThread() t.start() ''' Thread-1 拿到A鎖 Thread-1 拿到B鎖 Thread-1 拿到B鎖 Thread-2 拿到A鎖 然后就卡住,死鎖了 '''
解決方法,遞歸鎖,在Python中為了支持在同一線程中多次請求同一資源,python提供了可重入鎖RLock