python并发编程之多线程2------------死锁与递归锁,信号量等

一、死锁现象与递归锁

进程也是有死锁的

所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,

它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,

如下就是死锁

 1 死锁-------------------
 2 from  threading import Thread,Lock,RLock
 3 import time
 4 mutexA = Lock()
 5 mutexB = Lock()
 6 class MyThread(Thread):
 7     def run(self):
 8         self.f1()
 9         self.f2()
10     def f1(self):
11         mutexA.acquire()
12         print(‘\033[33m%s 拿到A锁 ‘%self.name)
13         mutexB.acquire()
14         print(‘\033[45%s 拿到B锁 ‘%self.name)
15         mutexB.release()
16         mutexA.release()
17     def f2(self):
18         mutexB.acquire()
19         print(‘\033[33%s 拿到B锁 ‘ % self.name)
20         time.sleep(1)  #睡一秒就是为了保证A锁已经被别人那到了
21         mutexA.acquire()
22         print(‘\033[45m%s 拿到B锁 ‘ % self.name)
23         mutexA.release()
24         mutexB.release()
25 if __name__ == ‘__main__‘:
26     for i in range(10):
27         t = MyThread()
28         t.start() #一开启就会去调用run方法

死锁现象

那么怎么解决死锁现象呢?

解决方法,递归锁:在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。

直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁

mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

 1 # 2.解决死锁的方法--------------递归锁
 2 from  threading import Thread,Lock,RLock
 3 import time
 4 mutexB = mutexA = RLock()
 5 class MyThread(Thread):
 6     def run(self):
 7         self.f1()
 8         self.f2()
 9     def f1(self):
10         mutexA.acquire()
11         print(‘\033[33m%s 拿到A锁 ‘%self.name)
12         mutexB.acquire()
13         print(‘\033[45%s 拿到B锁 ‘%self.name)
14         mutexB.release()
15         mutexA.release()
16     def f2(self):
17         mutexB.acquire()
18         print(‘\033[33%s 拿到B锁 ‘ % self.name)
19         time.sleep(1)  #睡一秒就是为了保证A锁已经被别人拿到了
20         mutexA.acquire()
21         print(‘\033[45m%s 拿到B锁 ‘ % self.name)
22         mutexA.release()
23         mutexB.release()
24 if __name__ == ‘__main__‘:
25     for i in range(10):
26         t = MyThread()
27         t.start() #一开启就会去调用run方法

解决死锁

二、信号量Semaphore(其实也是一把锁)

Semaphore管理一个内置的计数器

Semaphore与进程池看起来类似,但是是完全不同的概念。

进程池:Pool(4),最大只能产生四个进程,而且从头到尾都只是这四个进程,不会产生新的。

信号量:信号量是产生的一堆进程/线程,即产生了多个任务都去抢那一把锁

 1 from threading import Thread,Semaphore,currentThread
 2 import time,random
 3 sm = Semaphore(5) #运行的时候有5个人
 4 def task():
 5     sm.acquire()
 6     print(‘\033[42m %s上厕所‘%currentThread().getName())
 7     time.sleep(random.randint(1,3))
 8     print(‘\033[31m %s上完厕所走了‘%currentThread().getName())
 9     sm.release()
10 if __name__ == ‘__main__‘:
11     for i in range(20):  #开了10个线程 ,这20人都要上厕所
12         t = Thread(target=task)
13         t.start()

Semaphore举例

 1 hread-1上厕所
 2  Thread-2上厕所
 3  Thread-3上厕所
 4  Thread-4上厕所
 5  Thread-5上厕所
 6  Thread-3上完厕所走了
 7  Thread-6上厕所
 8  Thread-1上完厕所走了
 9  Thread-7上厕所
10  Thread-2上完厕所走了
11  Thread-8上厕所
12  Thread-6上完厕所走了
13  Thread-5上完厕所走了
14  Thread-4上完厕所走了
15  Thread-9上厕所
16  Thread-10上厕所
17  Thread-11上厕所
18  Thread-9上完厕所走了
19  Thread-12上厕所
20  Thread-7上完厕所走了
21  Thread-13上厕所
22  Thread-10上完厕所走了
23  Thread-8上完厕所走了
24  Thread-14上厕所
25  Thread-15上厕所
26  Thread-12上完厕所走了
27  Thread-11上完厕所走了
28  Thread-16上厕所
29  Thread-17上厕所
30  Thread-14上完厕所走了
31  Thread-15上完厕所走了
32  Thread-17上完厕所走了
33  Thread-18上厕所
34  Thread-19上厕所
35  Thread-20上厕所
36  Thread-13上完厕所走了
37  Thread-20上完厕所走了
38  Thread-16上完厕所走了
39  Thread-18上完厕所走了
40  Thread-19上完厕所走了

运行结果

三、Event

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

from threading import Event
Event.isSet() #返回event的状态值
Event.wait() #如果 event.isSet()==False将阻塞线程;
Event.set() #设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
Event.clear() #恢复

例如1.,有多个工作线程尝试链接MySQL,我们想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器,如果连接不成功,都会去尝试重新连接。那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作

 1 #首先定义两个函数,一个是连接数据库
 2 # 一个是检测数据库
 3 from threading import Thread,Event,currentThread
 4 import time
 5 e = Event()
 6 def conn_mysql():
 7     ‘‘‘链接数据库‘‘‘
 8     count = 1
 9     while not e.is_set():  #当没有检测到时候
10         if count >3: #如果尝试次数大于3,就主动抛异常
11             raise ConnectionError(‘尝试链接的次数过多‘)
12         print(‘\033[45m%s 第%s次尝试‘%(currentThread(),count))
13         e.wait(timeout=1) #等待检测(里面的参数是超时1秒)
14         count+=1
15     print(‘\033[44m%s 开始链接...‘%(currentThread().getName()))
16 def check_mysql():
17     ‘‘‘检测数据库‘‘‘
18     print(‘\033[42m%s 检测mysql...‘ % (currentThread().getName()))
19     time.sleep(5)
20     e.set()
21 if __name__ == ‘__main__‘:
22     for i  in range(3):  #三个去链接
23         t = Thread(target=conn_mysql)
24         t.start()
25     t = Thread(target=check_mysql)
26     t.start()

详看

2.例如2,红绿灯的例子

 1 from  threading import Thread,Event,currentThread
 2 import time
 3 e = Event()
 4 def traffic_lights():
 5     ‘‘‘红绿灯‘‘‘
 6     time.sleep(5)
 7     e.set()
 8 def car():
 9     ‘‘‘车‘‘‘
10     print(‘\033[42m %s 等绿灯\033[0m‘%currentThread().getName())
11     e.wait()
12     print(‘\033[44m %s 车开始通行‘ % currentThread().getName())
13 if __name__ == ‘__main__‘:
14     for i in range(10):
15         t = Thread(target=car)  #10辆车
16         t.start()
17     traffic_thread = Thread(target=traffic_lights)  #一个红绿灯
18     traffic_thread.start()

红绿灯

四、定时器(Timer)

指定n秒后执行某操作

from threading import Timer
def func(n):
    print(‘hello,world‘,n)
t = Timer(3,func,args=(123,))  #等待三秒后执行func函数,因为func函数有参数,那就再传一个参数进去
t.start()

五、线程queue

queue队列 :使用import queue,用法与进程Queue一样

queue.Queue(maxsize=0) #先进先出

1 # 1.队列-----------
2 import queue
3 q = queue.Queue(3) #先进先出
4 q.put(‘first‘)
5 q.put(‘second‘)
6 q.put(‘third‘)
7 print(q.get())
8 print(q.get())
9 print(q.get())

queue.LifoQueue(maxsize=0)#先进后出

1 # 2.堆栈----------
2 q = queue.LifoQueue() #先进后出(或者后进先出)
3 q.put(‘first‘)
4 q.put(‘second‘)
5 q.put(‘third‘)
6 q.put(‘for‘)
7 print(q.get())
8 print(q.get())
9 print(q.get())

queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列

 1 # ----------------
 2 ‘‘‘3.put进入一个元组,元组的第一个元素是优先级
 3 (通常也可以是数字,或者也可以是非数字之间的比较)
 4 数字越小,优先级越高‘‘‘
 5 q = queue.PriorityQueue()
 6 q.put((20,‘a‘))
 7 q.put((10,‘b‘))  #先出来的是b,数字越小优先级越高嘛
 8 q.put((30,‘c‘))
 9 print(q.get())
10 print(q.get())
11 print(q.get())

六、多线程性能测试

1.多核也就是多个CPU(1)cpu越多,提高的是计算的性能(2)如果程序是IO操作的时候(多核和单核是一样的),再多的cpu也没有意义。2.实现并发第一种:一个进程下,开多个线程第二种:开多个进程3.多进程:   优点:可以利用多核   缺点:开销大4.多线程   优点:开销小   缺点:不可以利用多核5多进程和多进程的应用场景   1.计算密集型:也就是计算多,IO少     如果是计算密集型,就用多进程(如金融分析等)   2.IO密集型:也就是IO多,计算少     如果是IO密集型的,就用多线程(一般遇到的都是IO密集型的)下例子练习:

 1 # 计算密集型的要开启多进程
 2 from  multiprocessing import Process
 3 from threading import Thread
 4 import time
 5 def work():
 6     res = 0
 7     for i in range(10000000):
 8         res+=i
 9 if __name__ == ‘__main__‘:
10     l = []
11     start = time.time()
12     for i in range(4):
13         p = Process(target=work)  #1.9371106624603271  #可以利用多核(也就是多个cpu)
14         # p  = Thread(target=work)  #3.0401737689971924
15         l.append(p)
16         p.start()
17     for p in l:
18         p.join()
19     stop = time.time()
20     print(‘%s‘%(stop-start))

计算密集型

 1 # I/O密集型要开启多线程
 2 from multiprocessing import Process
 3 from threading import Thread
 4 import time
 5 def work():
 6     time.sleep(3)
 7 if __name__ == ‘__main__‘:
 8     l = []
 9     start = time.time()
10     for i in range(400):
11         # p = Process(target=work)  #34.9549994468689   #因为开了好多进程,它的开销大,花费的时间也就长了
12         p = Thread(target=work) #2.2151265144348145  #当开了多个线程的时候,它的开销小,花费的时间也小了
13         l.append(p)
14         p.start()
15     for i in l :
16         i.join()
17     stop = time.time()
18     print(‘%s‘%(stop-start))

I/O密集型

七、python标准模块----concurrent.futures

时间: 2024-12-19 15:35:15

python并发编程之多线程2------------死锁与递归锁,信号量等的相关文章

Python并发编程04/多线程

目录 Python并发编程04/多线程 1.生产消费者模型 2.线程的理论知识 2.1什么是线程 2.2线程vs进程 2.3线程的应用 3.开启进程的两种方式 3.1第一种方式 3.2第一种方式 4.线程vs进程的代码对比 4.1开启速度对比 4.2对比pid 4.3同一个进程内线程共享内部数据 5.线程的其他方法 6.join与守护线程 6.1join 6.2守护线程 7.互斥锁 Python并发编程04/多线程 1.生产消费者模型 #编程思想,模型,设计模式,理论等等,都是交给你一种编程的方

python开发线程:死锁和递归锁&信号量&定时器&线程queue&事件evevt

一 死锁现象与递归锁 进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁 from threading import Thread,Lock import time mutexA=Lock() mutexB=Lock() class MyThread(Thread):

python并发编程之多线程(实践篇)

一.threading模块介绍 官网链接:https://docs.python.org/3/library/threading.html?highlight=threading# 1.开启线程的两种方式 #直接调用 import threading import time def run(n): print('task',n) time.sleep(2) t1 = threading.Thread(target=run,args=('t1',)) t1.start() #继承式调用 mport

python并发编程之多线程理论部分

阅读目录 一 什么是线程 二 线程的创建开销小 三 线程与进程的区别 四 为何要用多线程 五 多线程的应用举例 六 经典的线程模型(了解) 七 POSIX线程(了解) 八 在用户空间实现的线程(了解) 九 在内核空间实现的线程(了解) 十 用户级与内核级线程的对比(了解) 十一 混合实现(了解) 一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合到

python并发编程之多线程基础知识点

1.线程理论知识 概念:指的是一条流水线的工作过程的总称,是一个抽象的概念,是CPU基本执行单位. 进程和线程之间的区别: 1. 进程仅仅是一个资源单位,其中包含程序运行所需的资源,而线程就相当于车间的流水线,负责执行具代码. 2. 每个进程至少包含一个线程,由操作系统自动创建,称之为主线程 3. 每个进程可以有任意数量的线程 4.创建进程的开销要比创建进程小得多 5. 同一进程的线程间数据是共享的 6.线程之间是平等的,没有子父级关系,同一进程下的各线程的PID相同 7. 创建线程的代码可以写

python并发编程之多线程

一,什么是线程 线程也被称为轻量进程计算机科学术语,指运行中的程序的调度单位. 线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程.线程不拥有系统资源,只有运行必须的一些数据结构:它与父进程的其它线程共享该进程所拥有的全部资源.线程可以创建和撤消线程,从而实现程序的并发执行.一般,线程具有就绪.阻塞和运行三种基本状态. 二,线程与进程的区别 1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程. 2) 线程的划分尺度小于进程,使得多线程程序的并发性高. 3) 另外,

python 并发编程之多线程

一.线程理论 1.什么是线程 多线程(即多个控制线程)的概念是,在一个进程中存在多个线程,多个线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源. 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位. 2.进程与线程的区别 同一进程内的多个线程共享该进程内的地址资源 创建线程的开销要远小于创建进程的开销(创建一个进程,就是创建一个车间,涉及到申请空间,而且在该空间内建至少一条流水线,但创建线程,就只是在一个车间内造一

python并发编程:多线程-线程理论

一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程(流水线的工作需要电源,电源就相当于CPU),而一条流水线必须属于一个车间,一个车间的工作过程是一个进程,车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一条流水线. 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是CPU上的执行单位. 多新村(即多个控制线程)的概念时:在一个进程中存在多个线程,多个线程共享该进程的地址空间,

python并发编程:多线程-开启线程的两种方式

一 threading模块介绍 multiprocess模块完全模仿了threading模块的接口,二者在使用层面,有很大的相似性 二 开启线程的两种方式 方式一 from threading import Thread import time def sayhi(name): time.sleep(2) print("%s say hello" % name) if __name__ == '__main__': t = Thread(target=sayhi, args=('mik