多线程并发时会出现线程安全问题,如果不解决线程并发安全问题可能会让程序出现不可预料的情况。python提供了一些工具包来解决多线程安全问题,下面介绍其中常见的工具。
1.Threading.Lock() 锁的作用是将一段操作管理起来,确保每个时刻只有一个线程可以执行这段操作,这样就能确保里面的共享变量在并发的情况下对它的操作不会出现预期之外的一次。比如有多个售票窗口,如果不对售票操作加锁,就会导致出售的门票数大于可售的门票数。
lock常用方法: lock.acquire:获取锁,锁被其它线程持有时会被阻塞 lock.release:释放锁
import threading
import random
class WindowThread(threading.Thread):
def __init__(self,name):
threading.Thread.__init__(self,name=name)
self.name = name
self.tickts = 0
def run(self):
global tickt_count
while tickt_count > 0:
lock.acquire()
print('%s:有%d张票剩余 ' %(self.name,tickt_count))
if tickt_count > 2:
number = random.randint(1,2)
else:
number = 1
tickt_count -= number
self.tickts += number
print('%s 出售 %d 张票'
% (self.name, number))
lock.release()
print('%s :所有的票已经售罄'%self.name)
if __name__ == '__main__':
lock = threading.Lock()
tickt_count = 100
window1 = WindowThread('window1')
window2 = WindowThread('window2')
window3 = WindowThread('window3')
window1.start()
window2.start()
window3.start()
window1.join()
window2.join()
window3.join()
输出:
window1 :有100张票剩余
window1 出售 2 张票
...
window1 出售 2 张票
window2 出售 1 张票
window2 :有2张票剩余
window3 :有2张票剩余
window2 出售 1 张票
window3 出售 1 张票
window3 :所有的票已经售罄
window1 :所有的票已经售罄
window2 :所有的票已经售罄
2.Condition 条件变量允许一个或多个线程等待,直到它们被其它线程唤醒。Condition遵循上下文管理协议。
常见方法: acquire: 请求锁 release:释放锁 wait: 线程挂起,等待被唤醒(notify或notifyAll),可以设置等待超时时间 notify:唤醒等待线程,里面可以指定唤醒几个等待线程,比如设置n=3,则表示随机唤醒等待的三个线程。 notify_all: 唤醒所有的等待线程。
案例: condition版的生产者,消费者
import threading
import random
import time
gCondition = threading.Condition()
gMoney = 1000
gTimes = 0
totalTimes = 100
class Producer(threading.Thread):
def run(self):
global gMoney
global gTimes
while True:
with gCondition:
if gTimes <= totalTimes:
money = random.randint(100, 1000)
gMoney += money
print('生产者%s 生产了%s元' % (threading.current_thread(), money))
gTimes += 1
else:
break
# 通知等待线程
gCondition.notify_all()
time.sleep(0.5)
class Consumer(threading.Thread):
def run(self):
global gMoney
global gTimes
while True:
with gCondition:
money = random.randint(100, 1000)
while money > gMoney:
if gTimes > totalTimes:
return
print('消费者想要消费%s, 余额不足%s,等待...' % (money, gMoney))
gCondition.wait()
gMoney -= money
print('消费者%s消费了%s元' % (threading.current_thread(), money))
time.sleep(0.5)
def main():
for x in range(3):
t = Producer(name='producer_' + str(x))
t.start()
for x in range(5):
t = Consumer(name='consumer_' + str(x))
t.start()
if __name__ == '__main__':
main()
condition遵循上下文管理协议,所以可以结合with来使用,对于上下文管理协议可以看我之前的文章。with condition会在进入with之前自动执行condition.acquire,离开with的时候自动执行condition.release。
3.Event 事件对象管理一个内部标志,通过set()方法将其设置为True,并使用clear()方法将其设置为False。wait()方法阻塞,直到标志为True。该标志初始为False。
常用方法: is_set: 获取内部标志状态 set: 将内部标志设置为True。所有等待的线程都被唤醒 clear:将内部标志重置为False wait:阻塞直到内部标志为true,可以设置等待超时时间。
注意:wait不会将内部标志修改为false,如果内部标志本来就为true,调用wait不会被阻塞。
from threading import Thread, Event
import time
event=Event()
def light():
print('红灯等待')
time.sleep(3)
event.set()
def car(name):
print('%s正在等绿灯' %name)
event.wait()
print('%s通行' %name)
if __name__ == '__main__':
# 红绿灯
t1 = Thread(target=light)
t1.start()
# 车
for i in range(3):
t = Thread(target=car, args=('car-' + str(i),))
t.start()
输出:
红灯等待
car-0正在等绿灯
car-1正在等绿灯
car-2正在等绿灯
car-1通行
car-2通行
car-0通行
Event和condition最大的区别在于,condition调用wait的时候肯定会被阻塞,直到另外一个线程调用notify或notifyall将其唤醒,但是event不会调用wait不见得被阻塞,只有当内部标志为false的时候,event调用wait才会被阻塞。Event就好比十字路口的交通信号灯,绿灯的时候所有车辆必须通行(也就是没法阻塞,你不走后面的车主会揍你的),红灯的时候所有车辆都得等待。而condition就好比沉睡的公主,她睡着后(wait)必须有人将她唤醒(notify/notifyall),否则会一直沉睡(阻塞)。