一 死锁与递归锁
使用抢锁必须释放锁,其实在操作锁的时候也及其容易产生死锁现象(阻塞,即整个程序卡死)
案例说明:
from threading import Thread, Lock
import time
mutexA = Lock()
mutexB = Lock()
# 类只要加括号多次 产生的肯定是不同的对象
# 如果你想要实现多次加括号等到的是相同的对象 单例模式
class MyThead(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('%s 抢到A锁' % self.name) # 获取当前线程名
mutexB.acquire()
print('%s 抢到B锁' % self.name)
mutexB.release()
print('%s 释放了B锁'%self.name)
mutexA.release()
print('%s 释放了A锁'%self.name)
def func2(self):
mutexB.acquire()
print('%s 抢到B锁' % self.name)
time.sleep(2)
mutexA.acquire()
print('%s 抢到A锁' % self.name) # 获取当前线程名
mutexA.release()
print('%s 释放了A锁'%self.name)
mutexB.release()
print('%s 释放了B锁'%self.name)
if __name__ == '__main__':
for i in range(10):
t = MyThead()
t.start()
#结果展示
'''
Thread-1 抢到A锁
Thread-1 抢到B锁
Thread-1 释放了B锁
Thread-1 释放了A锁
Thread-1 抢到B锁
Thread-2 抢到A锁
此时阻塞住了
'''
#阻塞原因:
'''
此时线程2想要抢A锁,但是A锁在线程1手上,线程1此时想要抢B 锁,但是B锁在在线程2手上
所以此时产生了死锁现象,导致阻塞
'''
二 递归锁(了解)
递归锁的特点:
1、第一个抢到这把锁的人可以执行连续的acquire和 release操作(只能被第一个抢到锁的,且一个acquire对应一个 release)
2、它的内部有一个计数器 每acquire一次计数加一,每realse 一次计数减一
3、只要计数不为0,那么其它人都无法抢到该锁
mutexA = Lock()
mutexB = Lock()
# 换成
mutexA = mutexB = RLock() #递归锁
具体案例:
from threading import Thread, RLock
import time
# mutexA = Lock()
# mutexB = Lock()
mutexA = mutexB = RLock()
class MyThead(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('%s 抢到A锁' % self.name) # 获取当前线程名
mutexB.acquire()
print('%s 抢到B锁' % self.name)
mutexB.release()
print('%s 释放了B锁'%self.name)
mutexA.release()
print('%s 释放了A锁'%self.name)
def func2(self):
mutexB.acquire()
print('%s 抢到B锁' % self.name)
time.sleep(2)
mutexA.acquire()
print('%s 抢到A锁' % self.name) # 获取当前线程名
mutexA.release()
print('%s 释放了A锁'%self.name)
mutexB.release()
print('%s 释放了B锁'%self.name)
if __name__ == '__main__':
for i in range(3):
t = MyThead()
t.start()
#结果展示
'''
Thread-1 抢到A锁
Thread-1 抢到B锁
Thread-1 释放了B锁
Thread-1 释放了A锁
Thread-1 抢到B锁
Thread-1 抢到A锁
Thread-1 释放了A锁
Thread-1 释放了B锁
Thread-2 抢到A锁
Thread-2 抢到B锁
Thread-2 释放了B锁
Thread-2 释放了A锁
Thread-2 抢到B锁
Thread-2 抢到A锁
Thread-2 释放了A锁
Thread-2 释放了B锁
Thread-3 抢到A锁
Thread-3 抢到B锁
Thread-3 释放了B锁
Thread-3 释放了A锁
Thread-3 抢到B锁
Thread-3 抢到A锁
Thread-3 释放了A锁
Thread-3 释放了B锁
'''
三 信号量(了解)
信号量在不同的阶段可能对应不同的技术点
在并发编程中信号量指的是锁,相当于批量的锁
"""
如果我们将互斥锁比喻成一个坑位的话
那么信号量就相当于多个坑位
"""
from threading import Thread, Semaphore
import time
import random
sm = Semaphore(3) # 括号内写数字 写几就表示开设几个坑位
def task(name):
sm.acquire()
print('%s 正在蹲坑'% name)
time.sleep(random.randint(1, 5))
sm.release()
if __name__ == '__main__':
for i in range(9):
t = Thread(target=task, args=('伞兵%s号'%i, ))
t.start()
#结果展示
'''
伞兵0号 正在蹲坑
伞兵1号 正在蹲坑
伞兵2号 正在蹲坑
手动换行,上面三个线程是一块拿到锁后打印的
伞兵3号 正在蹲坑
伞兵4号 正在蹲坑
伞兵5号 正在蹲坑
伞兵6号 正在蹲坑
伞兵7号 正在蹲坑
伞兵8号 正在蹲坑
'''
四 Event事件(了解)
用于一些进程/线程需要等待另外一些进程/线程运行完毕之后才能运行,类似于发射信号一样
event 的方法使用:
event = Event() # 得到一个event事件对象
#在一个进程/线程中使用
event.wait() #表示此线程阻塞,等待其他线程释放可以运行的信号,即event.set()
#另一个进程/线程中使用
event.set() #表示此线程释放了运行信号,即其他由于 event.wait()阻塞的线程可以运行了
具体案例:红绿灯
from threading import Thread, Event
import time
event = Event() # 得到一个event对象,造了一个红绿灯
def light():
print('红灯亮着的')
time.sleep(3)
print('绿灯亮了')
event.set() # # 告诉等待红灯的人可以走了
def car(name):
print('%s 车正在灯红灯' % name)
event.wait() # 等待别人给你发信号,即程序运行了event.set()
print('%s 车加油门飙车走了' % name)
if __name__ == '__main__':
t = Thread(target=light)
t.start()
for i in range(5):
t = Thread(target=car, args=('%s' % i,))
t.start()
#结果展示
'''
红灯亮着的
0 车正在灯红灯
1 车正在灯红灯
2 车正在灯红灯
3 车正在灯红灯
4 车正在灯红灯
绿灯亮了
3 车加油门飙车走了
4 车加油门飙车走了
1 车加油门飙车走了
2 车加油门飙车走了
0 车加油门飙车走了
'''
五 线程q(了解)
同一个进程下多个线程数据是共享的,为什么在同一个进程下还是会去使用队列呢?
因为队列是管道 + 锁,所以使用队列是为了保证数据的安全
import queue
# 我们现在使用的队列都是只能在本地测试使用
# 1 队列q 先进先出
# q = queue.Queue(3)
# q.put(1)
# q.get()
# q.get_nowait()
# q.get(timeout=3)
# q.full()
# q.empty()
# 后进先出q(堆栈)
# q = queue.LifoQueue(3) # last in first out
# q.put(1)
# q.put(2)
# q.put(3)
# print(q.get()) # 3
# 优先级q 你可以给放入队列中的数据设置进出的优先级
q = queue.PriorityQueue(4)
q.put((10, '111'))
q.put((100, '222'))
q.put((0, '333'))
q.put((-5, '444'))
print(q.get()) # (-5, '444')
# put括号内放一个元祖 第一个放数字表示优先级
# 需要注意的是 数字越小优先级越高!!!