Python Thread虚假唤醒概念与防范详解
狂飙-高启强-张颂文-王天龙 人气:0什么是虚假唤醒
虚假唤醒是一种现象,它只会出现在多线程环境中,指的是在多线程环境下,多个线程等待在同一个条件上,等到条件满足时,所有等待的线程都被唤醒,但由于多个线程执行的顺序不同,后面竞争到锁的线程在获得时间片时条件已经不再满足,线程应该继续睡眠但是却继续往下运行的一种现象。
上面是比较书面化的定义,我们用人能听懂的话来介绍一下虚假唤醒。
多线程环境的编程中,我们经常遇到让多个线程等待在一个条件上,等到这个条件成立的时候我们再去唤醒这些线程,让它们接着往下执行代码的场景。假如某一时刻条件成立,所有的线程都被唤醒了,然后去竞争锁,因为同一时刻只会有一个线程能拿到锁,其他的线程都会阻塞到锁上无法往下执行,等到成功争抢到锁的线程消费完条件,释放了锁,后面的线程继续运行,拿到锁时这个条件很可能已经不满足了,这个时候线程应该继续在这个条件上阻塞下去,而不应该继续执行,如果继续执行了,就说发生了虚假唤醒。
import threading from threading import Condition class Data: def __init__(self, cond, num): self.num = num self.cond = cond def add(self): self.cond: Condition = self.cond self.cond.acquire() if self.num > 0: self.cond.wait() self.num += 1 print(threading.current_thread().getName(), self.num) self.cond.notifyAll() self.cond.release() def decr(self): self.cond: Condition = self.cond self.cond.acquire() if self.num == 0: self.cond.wait() self.num -= 1 print(threading.current_thread().getName(), self.num) self.cond.notifyAll() self.cond.release() if __name__ == '__main__': cond = Condition() num = 0 data = Data(cond, 0) thread_add = threading.Thread(name="A", target=data.add) thread_decr = threading.Thread(name="B", target=data.decr) thread_add.start() thread_decr.start()
现在改用4个线程
import threading from threading import Condition class Data: def __init__(self, cond, num): self.num = num self.cond = cond def add(self): self.cond: Condition = self.cond self.cond.acquire() if self.num > 0: self.cond.wait() self.num += 1 print(threading.current_thread().getName(), self.num) self.cond.notifyAll() self.cond.release() def decr(self): self.cond: Condition = self.cond self.cond.acquire() if self.num == 0: self.cond.wait() self.num -= 1 print(threading.current_thread().getName(), self.num) self.cond.notifyAll() self.cond.release() if __name__ == '__main__': cond = Condition() num = 0 data = Data(cond, 0) thread_add = threading.Thread(name="A", target=data.add) thread_decr = threading.Thread(name="B", target=data.decr) thread_add2 = threading.Thread(name="C", target=data.add) thread_decr2 = threading.Thread(name="D", target=data.decr) thread_add.start() thread_decr.start() thread_add2.start() thread_decr2.start()
还没有出现问题!!!
使用20个线程同时跑
import threading from threading import Condition class Data: def __init__(self, cond, num): self.num = num self.cond = cond def add(self): self.cond: Condition = self.cond self.cond.acquire() if self.num > 0: self.cond.wait() self.num += 1 print(threading.current_thread().getName(), self.num) self.cond.notifyAll() self.cond.release() def decr(self): self.cond: Condition = self.cond self.cond.acquire() if self.num == 0: self.cond.wait() self.num -= 1 print(threading.current_thread().getName(), self.num) self.cond.notifyAll() self.cond.release() if __name__ == '__main__': cond = Condition() num = 0 data = Data(cond, 0) for i in range(10): thread_add = threading.Thread(name="A", target=data.add) thread_add.start() for i in range(10): thread_decr = threading.Thread(name="B", target=data.decr) thread_decr.start()
这时就出现了问题!!!
现在改用while进行判断
防止虚假唤醒:
import threading from threading import Condition class Data: def __init__(self, cond, num): self.num = num self.cond = cond def add(self): self.cond: Condition = self.cond self.cond.acquire() # 这里采用了while进行判断,防止虚假唤醒 while self.num > 0: self.cond.wait() self.num += 1 print(threading.current_thread().getName(), self.num) self.cond.notifyAll() self.cond.release() def decr(self): self.cond: Condition = self.cond self.cond.acquire() # 这里采用了while进行判断,防止虚假唤醒 while self.num == 0: self.cond.wait() self.num -= 1 print(threading.current_thread().getName(), self.num) self.cond.notifyAll() self.cond.release() if __name__ == '__main__': cond = Condition() num = 0 data = Data(cond, 0) for i in range(10): thread_add = threading.Thread(name="A", target=data.add) thread_add.start() for i in range(10): thread_decr = threading.Thread(name="B", target=data.decr) thread_decr.start()
这个例子与上面的代码几乎没有差别,只是把if判断换成了while判断,所以每次萧炎和唐三醒过来之后都会再判断一下有没有苹果(唤醒自己的条件是否满足),如果不满足,就会继续睡下去,不会接着往下运行,从而避免了虚假唤醒。
总结
等待在一个条件上的线程被全部唤醒后会去竞争锁,所以这些线程会一个一个地去消费这个条件,等到后面的线程去消费这个条件时,条件可能已经不满足了,所以每个被唤醒的线程都需要再检查一次条件是否满足。如果不满足,应该继续睡下去;只有满足了才能往下执行。
加载全部内容