Python Thread虚假唤醒概念与防范详解

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Python Thread虚假唤醒概念与防范详解

狂飙-高启强-张颂文-王天龙   2023-03-23 我要评论

什么是虚假唤醒

虚假唤醒是一种现象,它只会出现在多线程环境中,指的是在多线程环境下,多个线程等待在同一个条件上,等到条件满足时,所有等待的线程都被唤醒,但由于多个线程执行的顺序不同,后面竞争到锁的线程在获得时间片时条件已经不再满足,线程应该继续睡眠但是却继续往下运行的一种现象。

上面是比较书面化的定义,我们用人能听懂的话来介绍一下虚假唤醒。

多线程环境的编程中,我们经常遇到让多个线程等待在一个条件上,等到这个条件成立的时候我们再去唤醒这些线程,让它们接着往下执行代码的场景。假如某一时刻条件成立,所有的线程都被唤醒了,然后去竞争锁,因为同一时刻只会有一个线程能拿到锁,其他的线程都会阻塞到锁上无法往下执行,等到成功争抢到锁的线程消费完条件,释放了锁,后面的线程继续运行,拿到锁时这个条件很可能已经不满足了,这个时候线程应该继续在这个条件上阻塞下去,而不应该继续执行,如果继续执行了,就说发生了虚假唤醒。

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判断,所以每次萧炎和唐三醒过来之后都会再判断一下有没有苹果(唤醒自己的条件是否满足),如果不满足,就会继续睡下去,不会接着往下运行,从而避免了虚假唤醒。

总结

等待在一个条件上的线程被全部唤醒后会去竞争锁,所以这些线程会一个一个地去消费这个条件,等到后面的线程去消费这个条件时,条件可能已经不满足了,所以每个被唤醒的线程都需要再检查一次条件是否满足。如果不满足,应该继续睡下去;只有满足了才能往下执行。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们