admin 管理员组

文章数量: 1087652

Python——信号量、条件变量、事件

1.信号量(Semaphore)

        信号量通常用于保护数量有限的资源,例如数据库服务器。在资源数量固定的任何情况下,都应该使用有界信号量。在生成任何工作线程前,应该在主线程中初始化信号量。

        信号量提供acquire方法和release方法,每当调用acquire方法的时候,如果内部计数器大于0,则将其减1,如果内部计数器等于0,则会阻塞该线程,知道有线程调用release方法将内部计数器更新到大于1位置。

        信号量也是一把锁,用来控制线程并发数。信号量通过一个内置的计数器,当加锁时,该数-1,释放时该数+1。计数器不能小于0,当计数器为0时,加锁操作会让线程进入阻塞状态,直到其他线程释放锁之后计数器有空位子。

        BoundedSemaphore与Semaphore的唯一区别在于前者将调用release()时检查计数器的值是否超过了初始值,如果超过了将抛出一个异常。

2.条件变量(Condition)

        有了互斥锁,为什么还需要条件变量?那是因为对于有些复杂问题,互斥锁搞不定。

        所谓条件变量(Condition),即这种机制是在满足了特定的条件后,线程才可以访问相关的数据。条件变量就相当于一个加了条件判断的锁,Condition通常与一个锁相关联。

        由Condition类来完成,除了可以像锁机制那样用,有acquire方法和release方法以外,它还有wait,notify,notifyAll方法。

相关方法:

  • acquire:加线程锁,调用关联的锁的相应方法。
  • release:释放线程锁。
  • wait([timeout]):线程挂起(阻塞),使线程进入Condition的等待池等待通知,并释放锁。直到一个notify通知或者超时才会被唤醒继续运行(超时参数默认不设置,可选填,浮点数,单位秒)。在使用前线程必须已获得锁定,否则将抛出异常。
  • notify(n=1):从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会释放锁定。在使用前线程必须已获得锁定,否则将抛出异常。
  • notifyAll():通知等待池中的所有线程,这些线程都将进入锁定池,并尝试获得锁定。调用这个方法不会释放锁定。在使用前线程时必须已获得锁定,否则将抛出异常。

注意:线程条件变量Condition中所有相关函数使用必须在acquire/release内部操作。

3.事件(Event)

        如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。

        对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象,而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行。

        事件对象管理一个内部标志,通过set()方法将其设置为True,使用clear()方法设置为False,wait()方法阻塞,直到标志为True,该标志缺省值为False。

        is_set:当且仅当内部标志为True时,返回True。
        set:将内部标志设置为True,所有等待它称为True的线程都被唤醒,当标志为True时,wait是不会阻塞的。
        clear:将内部标志重置为False,然后调用wait的线程将会阻塞。
        wait:阻塞直到内部标志为True。如果调用wait时内部标志为True,则立即返回True。wait方法总是返回True,除非设置了timeout并发生超时。

事件的几种用法:

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

4.按步骤解释如何使用这些信号量、条件变量、事件

(1)信号量:

import threading
import time
from sys import stdoutclass myThread(threading.Thread):def run(self):if semaphore.acquire():  # 加锁stdout.write(self.name+" "+time.ctime()+" \n")time.sleep(1)semaphore.release()  # 解锁if __name__ == "__main__":semaphore = threading.Semaphore(2)  # 创建锁对象,二者都可以threadLock = threading.Lock()list1 = []for i in range(10):list1.append(myThread())for i in list1:i.start()

运行结果:

Thread-1 Fri Jan 21 23:00:16 2022 
Thread-2 Fri Jan 21 23:00:16 2022 
Thread-3 Fri Jan 21 23:00:17 2022 
Thread-4 Fri Jan 21 23:00:17 2022 
Thread-6 Fri Jan 21 23:00:18 2022 
Thread-5 Fri Jan 21 23:00:18 2022 
Thread-7 Fri Jan 21 23:00:19 2022 
Thread-8 Fri Jan 21 23:00:19 2022 
Thread-10 Fri Jan 21 23:00:20 2022 
Thread-9 Fri Jan 21 23:00:20 2022

(2)条件变量:

import threadingclass XiaoAi(threading.Thread):def __init__(self, cond):super().__init__(name="小爱")self.cond = conddef run(self):self.cond.acquire()self.cond.wait()print("{} : 在".format(self.name))self.cond.notify()self.cond.wait()print("{} : 好啊".format(self.name))self.cond.notify()self.cond.wait()print("{} : 不聊了,再见".format(self.name))self.cond.notify()self.cond.release()class TianMao(threading.Thread):def __init__(self, cond):super().__init__(name="天猫精灵")self.cond = conddef run(self):self.cond.acquire()print("{} : 小爱同学".format(self.name))self.cond.notify()self.cond.wait()print("{} : 我们来对古诗吧".format(self.name))self.cond.notify()self.cond.wait()print("{} : 我住长江头".format(self.name))self.cond.notify()self.cond.wait()self.cond.release()if __name__ == "__main__":cond = threading.Condition()xiaoai = XiaoAi(cond)tianmao = TianMao(cond)# 启动顺序很重要xiaoai.start()tianmao.start()

运行结果:

天猫精灵 : 小爱同学
小爱 : 在
天猫精灵 : 我们来对古诗吧
小爱 : 好啊
天猫精灵 : 我住长江头
小爱 : 不聊了,再见

(3)事件:

"""
3.事件
"""import threading
import timedef Car():while True:if event.is_set():  # 如果设置了事件print("小车行驶")else:print("小车停止")event.wait()def set_event():while True:event.set()  # 设置事件time.sleep(1)event.clear()  # 把所设置的事件清除time.sleep(3)if __name__ == '__main__':event = threading.Event()  # 创建事件car1 = threading.Thread(target=Car)car1.start()set_e = threading.Thread(target=set_event)set_e.start()

运行结果:

小车行驶
······
小车行驶
小车停止

5.案例

        使用类继承的方式,实现信号量、事件功能操作。具体案例:第一个 线程中获取当前时间,判断当前时间3秒之后,触发“事件”对象。在另 一个线程中,作为数学考试结束的判断变量,否则一直处于考试中,并打印。

        SCE:

"""
1.信号量、条件变量和事件的使用
要求:使用类继承的方式,实现信号量、事件功能操作。具体案例:第一个线程中获取当前时间,判断当前时间3秒之后,触发“事件”对象。在另一个线程中,作为数学考试结束的判断变量,否则一直处于考试中,并打印。
"""import threading
import time# 自定义事件监听类继承于Thread类
class Time(threading.Thread):# 构造方法,重写Thread类的构造方法,并初始化self属性def __init__(self):super(Time, self).__init__()# 重写Thread类的run方法def run(self):for i in range(5):# 输出即时时间print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))# 使线程休眠3秒time.sleep(3)# 设置事件响应event.set()# 自定义考试线程类继承于Thread类
class Exam(threading.Thread):# 构造方法def __init__(self):super(Exam, self).__init__()# 重写Thread类的run方法def run(self):# 循环判断事件是否响应while True:print("正在进行数学考试中,请勿打扰!")  # 输出“正在进行数学考试中,请勿打扰!”time.sleep(2)if event.is_set():  # 如果事件响应print("数学考试已结束,请停止答题!")  # 输出“数学考试已结束,请停止答题!”breakif __name__ == '__main__':# 初始化event对象event = threading.Event()# 初始化线程对象并调用start方法exam = Exam()exam.start()time = Time()time.start()

        运行结果:

正在进行数学考试中,请勿打扰!
2022-01-21 23:27:48
正在进行数学考试中,请勿打扰!
2022-01-21 23:27:51
正在进行数学考试中,请勿打扰!
2022-01-21 23:27:54
正在进行数学考试中,请勿打扰!
正在进行数学考试中,请勿打扰!
2022-01-21 23:27:57
正在进行数学考试中,请勿打扰!
2022-01-21 23:28:00
正在进行数学考试中,请勿打扰!
正在进行数学考试中,请勿打扰!
数学考试已结束,请停止答题!

本文标签: Python信号量条件变量事件