一、概念
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 importProcess,Lockimportos,time,randomdef 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 isdone11748 isrunning11748 isdone6184 isrunning6184 is done
同步锁实例
线程互斥锁
我们知道线程是共享一个进程的地址空间的,改变地址空间中的环境变量就会改变所有线程的环境变量,但是如果我们想用线程来更改环境变量,必须使用互斥锁。
#线程互斥锁
from threading importThread,Lockimporttime
n= 100
deftask():globaln
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 inlst:
t.join()print(n)
线程锁实例
这里可以用一个例子来说明Lock和join的关系
如果使用join,那么就只有几个人能买到票,前面的没买到,后面的查票都不行,这个添加到查询上面的的锁不合适
from multiprocessing importProcessimportjson,random,time,osdef 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'] -=1time.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())deftask():
search()
get()if __name__ == "__main__":for i in range(10):
p= Process(target=task)
p.start()
p.join()#使用join永远只有第一个人能够抢到#执行结果:
2408 剩余票数 22408抢票成功11328 剩余票数 111328抢票成功12364剩余票数 016460剩余票数 011848剩余票数 011820剩余票数 014792剩余票数 014084剩余票数 05276剩余票数 06160 剩余票数 0
使用join
使用Lock就会相对好一些
from multiprocessing importProcess,Lockimportjson,random,time,osdef 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'] -=1time.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())deftask(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 剩余票数 313232抢票成功12368 剩余票数 2
10916 剩余票数 2
9156 剩余票数 2
9864 剩余票数 214856抢票成功
12968抢票成功
使用Lock机制
可以看出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 importProcessfrom threading importThreadimportos,timedefwork():
res=0for i in range(100000000):
res*=iif __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 inl:
p.join()
stop=time.time()print('run time is %s' %(stop-start))
计算密集型:多进程效率高
from multiprocessing importProcessfrom threading importThreadimportthreadingimportos,timedefwork():
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 inl:
p.join()
stop=time.time()print('run time is %s' %(stop-start))
I/O密集型:多线程效率高
三、线程和进程的开启方式
开启进程有两种方式:
#第一种方式:
from multiprocessing importProcessimporttime,randomdeftask(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 importProcessimporttimeclassMyclass(Process):def __init__(self,name):
super().__init__()
self.name=namedefrun(self):
time.sleep(2)print('%s is running'%self.name)if __name__ == '__main__':
p= Myclass('cx')
p.start()#开启进程的第二种方式,之前给Procee传参,现在是使用继承
进程开启方式实例
同样的,开启线程也有两种方式
#第一种方式
from threading importThreadimporttime,osdeftask():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 importThreadimporttime,osclassMythread(Thread):def __init__(self,name):
super().__init__()
self.name=namedefrun(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 importProcessdeftask():globaln
n=0
n= 100
if __name__ =="__main__":
p= Process(target=task)
p.start()print(n)#执行结果
100
进程名称空间实例
#线程
from threading importThreaddeftask():globaln
n=0
n= 100
if __name__ =="__main__":
t= Thread(target=task)
t.start()print(n)#执行结果
0
线程名称空间实例
五、进程池和线程池
进程池内部维护一个进程序列,当使用时,则去进程池中的获取一个进程,如果进程池序列中没有可供使用的进程,那么进程就会等待,直到进程池中有可用进程为止,线程池也是类似。
进程池:
from concurrent.futures importProcessPoolExecutorimportos,randomdeffunc(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 importThread,current_threadfrom concurrent.futures importProcessPoolExecutor,ThreadPoolExecutorimporttime,osdeftask(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 inobjs:print(obj.result())print("主",current_thread().getName())#执行结果
ThreadPoolExecutor-0_0 isrunning
ThreadPoolExecutor-0_1 isrunning
ThreadPoolExecutor-0_2 isrunning
ThreadPoolExecutor-0_2 isrunning
ThreadPoolExecutor-0_1 isrunning
ThreadPoolExecutor-0_0 isrunning
01
4ThreadPoolExecutor-0_1 isrunning9
16
25
36主 MainThread
线程池实例
六、守护进程和守护线程
无论是进程还是线程,当主进程结束的时候,守护进程也会随之结束。
对于进程来说,所谓的主进程结束就是不包括子进程;对于线程来说,所谓的主进程结束是包括其中线程都结束。
守护进程:守护进程必须在子线程开启之前设置,而且守护进程不能有子进程。
#守护进程
from multiprocessing importProcessimportos,time,randomdeftask():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 importProcessimportos,time,randomdeftask():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 importThreadimportos,time,randomdeftask():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 importThreadimportos,time,randomdeftask():print('%s is running'%os.getpid())
time.sleep(2)print('%s is done' %os.getppid())defmask():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 isrunning15896start....
主14716 isdone15896over....#由于子进程的执行时间非常长,因此守护线程也能全部执行完
与守护进程不同的是,守护线程可以有子进程from threading importThreadimportos,time,randomdeftask():
c= Thread(target=mask)
c.start()print('%s is running'%os.getpid())
time.sleep(2)print('%s is done' %os.getppid())defmask():print("守护线程中的子线程")if __name__ == "__main__":
p= Thread(target=task)
p.daemon=True
p.start()#守护线程里面可以再开子线程
time.sleep(3) #需要主线程的执行时间稍微长一点
print("主")
守护线程实例
七、进程队列和线程队列
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocess模块支持两种形式:队列和管道。
这两种方式都是使用消息传递的。队列是基于管道和锁来实现。
进程队列
from multiprocessing importQueue
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)
everythingis possible
进程队列示例
线程队列
用法基本与进程队列一样
有几种模式常用:先进先出、后进先出、优先级。
importqueue#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
先进先出实例
#栈
importqueue
q=queue.LifoQueue()
q.put("one")#可以放任意类型的数据
q.put("two")
q.put("three")print(q.get())print(q.get())print(q.get())#执行结果
three
two
one
后进先出
#优先级
importqueue
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 importProcessPoolExecutorimportos,time,randomdefjob(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.....4156maoxiantart.....5544kebitart.....3084xiaoniaotart.....4156maoxiantart.....5544kebitart.....3084maoxiantart.....4156xiaoniaotart.....5544kebitart.....3084kebitart.....4156
进程池实例
#10个人要上厕所,厕所只有三个
from multiprocessing importProcess,Semaphoreimporttime,random,osdeftask(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上厕所37868上厕所25888上厕所25736上厕所210440上厕所215564上厕所116320上厕所114360上厕所21824上厕所26528 上厕所3
信号量实例
九、生产者消费者模型
1.为什么要使用生产着消费者模型
在线程世界里,生产者就是生产数据的线程,消费者就是消费消费数据的线程。
在多进程开发当中,如果生产者处理很快,消费者处理很慢,那么生产者就必须等待消费者处理完,才能继续生产数据,
同样的道理,如果消费者的处理能力大于生产者。为了解决生产者和消费者强耦合造成的时间浪费问题,引入了生产者和消费者模型
2.什么是生产者消费者模型
生产者消费者模型是通过一个容器来解决生产者和消费者的强耦合问题,
生产者和消费者彼此之间不能直接通讯,而通过阻塞队列来通讯,所以生产者生产完数据之后不用等待消费者处理,直到仍给阻塞队列,
消费者不找生产者要数据,而是直接从阻塞队列取值,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
3.实例
importtime,os,randomfrom multiprocessing importQueue,Processdef 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): #定义一个消费者函数
whileTrue:
res=q.get()if res isNone: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个包子
生产者消费者实例
importtime,os,randomfrom multiprocessing importQueue,Process,JoinableQueuedef 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): #定义一个消费者函数
whileTrue:
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 importThread,Lockimporttime
mutexA=Lock()
mutexB=Lock()classMyThread(Thread):defrun(self):
self.func1()
self.func2()deffunc1(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()deffunc2(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