多线程
1.信号量
信号量可以理解为多把锁,同时允许多个线程来更改数据。信号量是用来控制线程并发数的Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。
import threading
import time
def run(n, x):
semaphore.acquire() # 2.给线程上锁
print(n)
time.sleep(x)
semaphore.release() # 3.将锁释放
if __name__ == '__main__':
semaphore = threading.Semaphore(5) # 1.先实例化一把锁,传入参数为几则同时运行几个线程
for i in range(16):
t = threading.Thread(target=run, args=(i, i))
t.start()
注:当i=0时,线程耗费0秒就结束了,则同时运行线程1、2、3、4、5。一旦同时运行的线程数不够五个,就会将锁释放,往下进行下一个线程。
2. 条件变量
(1)线程条件变量的相关函数
acquire() — 线程锁,注意线程条件变量Condition中的所有相关函数使用必须在acquire() /release() 内部操作;
release() — 释放锁,注意线程条件变量Condition中的所有相关函数使用必须在acquire() /release() 内部操作;
wait(timeout) — 线程挂起(阻塞状态),直到收到一个notify通知或者超时才会被唤醒继续运行(超时参数默认不设置,可选填,类型是浮点数,单位是秒)。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError;
notify(n=1) — 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,缺省参数,默认是通知一个正等待通知的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError,notify()不会主动释放Lock;
notifyAll() — 如果wait状态线程比较多,notifyAll的作用就是通知所有线程.
(2)线程条件变量原理
条件变量可以在并行访问共享资源时,保护共享资源,防止出现脏数据。同时条件变量自身提供了wait/notify/notifyAll方法,用于阻塞/通知其他并行线程,可以访问共享资源了。可以这么理解,条件变量提供了一种多线程通信机制,假如线程1需要数据,那么线程1就阻塞等待,这时线程2就去制造数据,线程2制造好数据后,通知线程1可以去取数据了,然后线程1去获取数据。
(3)线程条件变量的使用
import threading
import time
def run(x):
con.acquire() #线程上锁
print(f'线程{x}')
con.notify() #唤醒正在等待的线程
print(f'线程{x}挂起')
con.wait() #等待对方回应消息,使用wait阻塞本线程,等待对方通过notify唤醒本线程
time.sleep(1)
print(f'线程{x}再次启动')
con.notify() #唤醒对方
con.release() #线程释放锁
if __name__ == '__main__':
con = threading.Condition() #创建条件变量
for i in range(10):
t = threading.Thread(target=run, args=(i, ))
t.start()
使用两个线程,更加清晰的展现条件变量整个程序执行的顺序
(在没有使用条件变量的情况下,两个线程应该时同时启动)
import threading
import time
def run1(x):
con.acquire() # 2.线程上锁,则先只运行线程1
print(f'线程{x}')
con.notify() # 3.唤醒线程2(发出信号,通知线程2可以开始运行了,但并不影响线程1的运行)
print(f'线程{x}挂起')
con.wait() # 5.线程1停止
time.sleep(1) # 7.线程1被唤醒,执行语句(暂停一秒)
print(f'线程{x}再次启动')
con.notify() # 9.唤醒线程2
con.release()
def run2(x):
con.acquire()
print(f'线程{x}')
con.notify() # 6.唤醒线程1,但不影响线程2自身的运行
print(f'线程{x}挂起')
con.wait() # 8.线程2停止
time.sleep(1) # 10.线程2被唤醒,执行语句(暂停一秒)
print(f'线程{x}再次启动')
con.notify() # 11.若还有其他线程,则唤醒下一个线程
con.release()
if __name__ == '__main__':
con = threading.Condition() #创建条件变量
t1 = threading.Thread(target=run1, args=(1, ))
t1.start() # 1.线程1开始运行
t2 = threading.Thread(target=run2, args=(2, ))
t2.start() # 4.线程2开始运行
3.事件
事件,就是多线程之间的通信。事件对象管理一个内部标志,通过set()
方法将其设置为True
,并使用clear()
方法将其设置为False
。wait()
方法阻塞,直到标志为True
。该标志初始为False
。
线程事件的相关函数:
is_set()
当且仅当内部标志为True
时返回True
。
set()
将内部标志设置为True
。所有等待它成为True
的线程都被唤醒。当标志保持在True
的状态时,线程调用wait()
是不会阻塞的。
clear()
将内部标志重置为False
。随后,调用wait()
的线程将阻塞,直到另一个线程调用set()
将内部标志重新设置为True
。
wait(timeout=None)
阻塞直到内部标志为真。如果内部标志在wait()
方法调用时为True
,则立即返回。否则,则阻塞,直到另一个线程调用set()
将标志设置为True
,或发生超时。
该方法总是返回True
,除非设置了timeout
并发生超时。
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()