信号量通常用于保护数量有限的资源,例如数据库服务器。在资源数量固定的任何情况下,都应该使用有界信号量。在生成任何工作线程前,应该在主线程中初始化信号量。
信号量提供acquire方法和release方法,每当调用acquire方法的时候,如果内部计数器大于0,则将其减1,如果内部计数器等于0,则会阻塞该线程,知道有线程调用release方法将内部计数器更新到大于1位置。
信号量也是一把锁,用来控制线程并发数。信号量通过一个内置的计数器,当加锁时,该数-1,释放时该数+1。计数器不能小于0,当计数器为0时,加锁操作会让线程进入阻塞状态,直到其他线程释放锁之后计数器有空位子。
BoundedSemaphore与Semaphore的唯一区别在于前者将调用release()时检查计数器的值是否超过了初始值,如果超过了将抛出一个异常。
有了互斥锁,为什么还需要条件变量?那是因为对于有些复杂问题,互斥锁搞不定。
所谓条件变量(Condition),即这种机制是在满足了特定的条件后,线程才可以访问相关的数据。条件变量就相当于一个加了条件判断的锁,Condition通常与一个锁相关联。
由Condition类来完成,除了可以像锁机制那样用,有acquire方法和release方法以外,它还有wait,notify,notifyAll方法。
注意:线程条件变量Condition中所有相关函数使用必须在acquire/release内部操作。
如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用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并发生超时。
事件的几种用法:
import threading
import time
from sys import stdout
class 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
import threading
class XiaoAi(threading.Thread):
def __init__(self, cond):
super().__init__(name="小爱")
self.cond = cond
def 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 = cond
def 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.事件
"""
import threading
import time
def 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()
运行结果:
小车行驶
······
小车行驶
小车停止
使用类继承的方式,实现信号量、事件功能操作。具体案例:第一个 线程中获取当前时间,判断当前时间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("数学考试已结束,请停止答题!") # 输出“数学考试已结束,请停止答题!”
break
if __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
正在进行数学考试中,请勿打扰!
正在进行数学考试中,请勿打扰!
数学考试已结束,请停止答题!