死锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁。
from threading import Thread, Lock import time class MyRLock(Thread): mexty = Lock() mexty2 = Lock() def run(self): self.func() self.func2() def func(self): self.mexty.acquire() print('%s 拿到 A 锁' % self.name) self.mexty2.acquire() print('%s 拿到 B 锁' % self.name) self.mexty2.release() self.mexty.release() def func2(self): self.mexty2.acquire() print('%s 拿到 B 锁' % self.name) time.sleep(0.5) self.mexty.acquire() print('%s 拿到 A 锁' % self.name) self.mexty.release() self.mexty2.release() if __name__ == '__main__': for i in range(10): t = MyRLock() t.start()
执行结果:出现死锁,整个程序阻塞住
# Thread-1 拿到 A 锁 # Thread-1 拿到 B 锁 # Thread-1 拿到 B 锁 # Thread-2 拿到 A 锁
递归锁
解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次。
递归锁可以连续 acquire 多次,每 acquire 一次计数器则 +1,只有计数为 0 时,才能被抢到 acquire。
from threading import Thread, RLock import time class MyRLock(Thread): mexty = mexty2 = RLock() def run(self): self.func() self.func2() def func(self): self.mexty.acquire() print('%s 拿到 A 锁' % self.name) self.mexty2.acquire() print('%s 拿到 B 锁' % self.name) self.mexty2.release() self.mexty.release() def func2(self): self.mexty2.acquire() print('%s 拿到 B 锁' % self.name) time.sleep(0.5) self.mexty.acquire() print('%s 拿到 A 锁' % self.name) self.mexty.release() self.mexty2.release() if __name__ == '__main__': for i in range(10): t = MyRLock() t.start()
信号量
信号量也是一把锁,可以指定信号量为5,对比互斥锁同一时间只能有一个任务抢到锁去执行,信号量同一时间可以有5个任务拿到锁去执行,如果说互斥锁是合租房屋的人去抢一个厕所,那么信号量就相当于一群路人争抢公共厕所,公共厕所有多个坑位,这意味着同一时间可以有多个人上公共厕所,但公共厕所容纳的人数是一定的,这便是信号量的大小。
解析
# Semaphore管理一个内置的计数器, # 每当调用acquire()时内置计数器-1; # 调用release() 时内置计数器+1; # 计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
from threading import Thread, Semaphore, currentThread import time import random sm = Semaphore(3) def func(): with sm: print('%s 正在座位上' % currentThread().getName()) time.sleep(random.randint(1, 3)) if __name__ == '__main__': for i in range(10): s = Thread(target=func,) s.start()
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():恢复event的状态值为False。
E.wait() 可以设置等待时间,即超过此时间为超时,不在等待。
from threading import Thread, Event import time E = Event() def student(name): print('%s 学生正在上课' % name) E.wait() # E.wait(1) print('%s 学生下课' % name) def teacher(name): print('%s 老师正在上课' % name) time.sleep(3) print('%s 老师正在下课' % name) E.set() if __name__ == '__main__': s = Thread(target=student, args=('stu',)) s2 = Thread(target=student, args=('stu2',)) s3 = Thread(target=student, args=('stu3',)) t = Thread(target=teacher, args=('ysg',)) lis = [s, s2, s3, t] for i in lis: i.start()
执行结果:
# E.wait() # stu 学生正在上课 # stu2 学生正在上课 # stu3 学生正在上课 # ysg 老师正在上课 # ysg 老师正在下课 # stu2 学生下课 # stu3 学生下课 # stu 学生下课 # E.wait(1) # stu 学生正在上课 # stu2 学生正在上课 # stu3 学生正在上课 # ysg 老师正在上课 # stu 学生下课 # stu2 学生下课 # stu3 学生下课 # ysg 老师正在下课
模拟多个客户端连接服务端,限制连接次数
from threading import Thread, Event, currentThread import time E = Event() def client(): n = 0 while not E.is_set(): print('%s 正在连接' % currentThread().getName()) E.wait(0.5) n += 1 if n == 3: print('重试次数超限') return print('连接成功') def check(): # 检测服务端是否运行 print('%s 正在检测' % currentThread().getName()) time.sleep(5) E.set() print('检测完成') if __name__ == '__main__': for i in range(3): c = Thread(target=client, ) c.start()
定时器
定时器,指定n秒后执行某操作
from threading import Timer def func(name): print('hello %s' % name) if __name__ == '__main__': t = Timer(2, func, args=('ysg',)) t.start()
验证码小例子
from threading import Timer import random class Timers: def __init__(self): super().__init__() self.myTimer() def myTimer(self): self.info = self.make_timer() print(self.info) self.t = Timer(5, self.myTimer) self.t.start() def make_timer(self, num=6): lis = '' for i in range(num): s1 = str(random.randint(0, 9)) s2 = chr(random.randint(65, 90)) lis += random.choice([s1, s2]) return lis def run(self): while True: res = input('>>>').strip() if res == self.info: print('验证成功') self.t.cancel() break t = Timers() t.run()
线程queue
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
有三种不同的用法
class queue.Queue(maxsize=0) # 队列:先进先出
import queue q = queue.Queue(3) q.put('hello') q.put(123) q.put([1, 2, 3]) # q.put(456,block=False) # 队列长度为 3,再继续放,报错:queue.Full print(q.get()) print(q.get()) print(q.get()) # print(q.get(block=False)) # 队列中数据已全部取走,再继续取,报错:queue.Empty # 输出结果 先进先出 # hello # 123 # [1, 2, 3]
class queue.LifoQueue(maxsize=0) #堆栈:last in fisrt out
注意:用法与队列一样
import queue q = queue.LifoQueue(3) q.put('hello') q.put(123) q.put([1, 2, 3]) print(q.get()) print(q.get()) print(q.get()) # 输出结果 先进先出 # [1, 2, 3] # 123 # hello
class queue.PriorityQueue(maxsize=0) #优先级队列:存储数据时可设置优先级的队列
import queue q = queue.PriorityQueue(3) q.put((10, 'hello')) q.put((50, 123)) q.put((45, [1, 2, 3])) print(q.get()) print(q.get()) print(q.get()) # 执行结果 按照优先级取出 # (10, 'hello') # (45, [1, 2, 3]) # (50, 123)