信号量
什么是信号量
信号量是操作系统提供给用户使用的一种机制,帮助用户进程协调使用资源,用户编程的时候可以直接调用,不必自己设计。信号量主要保护共享资源的,确保该资源在同一时刻只有一个或多个线程占用。
换句话说它就是控制多线程共同访问共享资源的一种手段。
信号量的使用
threading模块里的Semaphore类实现了信号量对象,可用于控制获取资源的线程数量。所具有的acquire()和release()方法,可以用with语句的上下文管理器。当进入时,将调用acquire()方法,当退出时,将调用release()。
下面给出一段示例
import threading
import time
def run(n,x):
semaphore.acquire() #进入信号量
print(n)
time.sleep(x)
semaphore.release() #退出信号量
if __name__=='__main__':
semaphore = threading.Semaphore(5) # 可以在函数中规定要运行的线程数量,比如现在函数中的参数为5,就会一次运行5个线程,当运行的数量不够参数的值时,就会顺着继续往下取
for i in range(50):
t = threading.Thread(target=run,args=(i,i))
t.start()
代码解释
从代码中,我们知道在主函数中定义信号量semaphore对象。在run方法中通过调用acquire(),进入到信号量中,在完成线程的一些列语句后,调用release(),退出信号量。其中在主函数中设定的参数5,意味着每次都运行5个线程,每当一个线程结束后,都会在向下继续步入一个线程。
条件变量
什么是条件变量
条件变量主要是简化互斥锁中一些比较难以实现或者实现的效率太低的一个封装对象。完成线程的同步问题。Condition被称为条件变量
条件变量的方法
acquire()/release(): 调用关联的锁的相应方法。
wait(): 调用这个方法将使线程进入Condition的等待池等待通知,并释放锁。
使用前线程必须已获得锁定,否则将抛出异常。
notify(): 调用这个方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用
acquire():尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会
释放锁定。使用前线程必须已获得锁定,否则将抛出异常。
notifyAll(): 调用这个方法将通知等待池中所有的线程,这些线程都将进入锁定池
尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。
下面给出一段示例
import threading
import time
def run(x):
# lock.acquire()
con.acquire()
print(f'线程{x}')
con.notify() # 让下一个在等待的线程启动,对这个线程本身没有影响,是唤起了另一个线程
print(f'线程{x}挂起')
con.wait() # 将线程等待了
time.sleep(1)
print(f'线程{x}再次启动')
con.notify() # 让下一个在等待的线程启动
con.release()
# lock.release()
def run2(x):
# lock.acquire()
con.acquire()
print(f'线程{x}')
con.notify() # 让下一个在等待的线程启动
print(f'线程{x}挂起')
con.wait() # 将线程等待了
time.sleep(1)
print(f'线程{x}再次启动')
con.notify() # 让下一个在等待的线程启动
con.release()
# lock.release()
if __name__=='__main__':
# lock = threading.Lock()
con = threading.Condition() #条件变量
# for i in range(10):
# t = threading.Thread(target=run,args=(i,))
# t.start()
t1 = threading.Thread(target=run,args=(1,))
t2 = threading.Thread(target=run2,args=(2,))
t1.start()
t2.start()
代码解释
在代码中设定了,两个方法,分别对应着主函数里面的两个线程,每个线程调用一个方法。con = threading.Condition() ,创建条件变量。调用 **acquire()**进入条件变量。在函数中打印,线程x,然后使用 **notify()**方法,唤醒另一个线程,打印线程挂起,然后等待。另一个线程被唤起后继续执行,唤起另一个线程后,挂起,另一个线程打印 再次启动,然后调用 **release()**结束条件变量。
事件
什么是事件
事件event中有一个全局内置标志Flag,值为 True 或者False。使用wait()函数的线程会处于阻塞状态,此时Flag指为False,直到有其他线程调用set()函数让全局标志Flag置为True,其阻塞的线程立刻恢复运行,还可以用isSet()函数检查当前的Flag状态.
相关方法介绍
set() : 全局内置标志Flag,将标志Flag 设置为 True,通知在等待状态(wait)的线程恢复运行;
isSet() : 获取标志Flag当前状态,返回True 或者 False;
wait(): 一旦调用,线程将会处于阻塞状态,直到等待其他线程调用set()函数恢复运行;
clear() : 将标志设置为False;
下面给出一段示例
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(1)
if __name__=='__main__':
event = threading.Event()
car1 = threading.Thread(target=car)
car1.start()
set_e = threading.Thread(target=set_event)
set_e.start()
代码解释
首先,在主函数中定义事件,并完成两个线程的编写。同时,创建两个方法。在set_event方法中通过set方法设置事件,然后让线程沉睡,之后通过clear清除事件。与此同时方法car中的线程也在不断的执行,通过is_set判断是否有事件,如果有则打印小车行驶,如果没有打印小车停止并让线程等待。注意,在线程等待时,循环还在继续,也就是说,还在不断的判断有没有事件,当另一个线程创建了事件,就可以继续打印。
综合作业题
题目
使用类继承的方式,实现信号量、事件功能操作。具体案例:第一个线程中获取当前时间,判断当前时间3秒之后,触发“事件” 对象。在另一个线程中,作为数学考试结束的判断变量,否则一直处于考试中,并打印。
代码
import time
import threading
import datetime
class Thread1(threading.Thread): #继承父类threading.Thread
def __init__(self):
super(Thread1, self).__init__()
def run(self): #重构run函数必须写
while True:
print(datetime.datetime.now()) # 打印当前时间
time.sleep(3) #睡眠3秒
event.set() # 设置事件
time.sleep(5) # 线程睡眠5秒
event.clear() #清除事件
class Thread2(threading.Thread): #继承父类threading.Thread
def __init__(self):
super(Thread2, self).__init__()
def run(self): #重构run函数必须写
while True:
if event.isSet(): # 判断是否有事件
print('数学考试结束')
break
else:
print('数学考试进行中。。。')
event.wait() # 事件等待了,但是还是在一直检测
if __name__=='__main__':
event = threading.Event() # 创建事件对象
t1 = Thread1() #实例化对象线程一
t2 = Thread2() #实例化对象线程二
t1.start() #开启线程一
t2.start() #开启线程二