这部分的内容如果你学过操作系统的话会很快理解,如果没有学过操作系统可以先大致看看,我尽量讲清楚,如果还不清楚,可以跳过去看我下面的Thrading常用用法:
竞争
我们以前所编写的程序都是独立的,线程之间没有共享的数据或者共享的数据是不可变动的类型。然而如果线程之间需要共享的是可变动状态的数据,就有可能发生竞争条件,例子如下;
import threading
def setTo1(data):
while True:
data['Justin']=1
if data['Justin'] !=1:
raise ValueError('setTo1 的数据不一致:{}'.format(str(data)))
def setTo2(data):
while True:
data['Justin']=2
if data['Justin']!=2:
raise ValueError('setTo2 的数据不一致:{}'.format(str(data)))
data={}
a=threading.Thread(target=setTo1, args=(data,))
b=threading.Thread(target=setTo2, args=(data,))
a.start()
b.start()
尝试多运行几下,一定会报错,这是因为,a,b两个线程同时对一个全局字典进行设置,一个将对应值设置为1,另一个将对应值设置为2,而同一时间python解释器只允许一个线程执行,他会在线程之间进行切换,切换的时间点我们无法预测。如果a在setTo1()函数中data['justin']=1执行完后,解释器切换到了b,这时正好执行了b的if datap['justin']!=2:该句话,刚好这句就会成立,引发例外。
锁定
既然出现了上面竞争行为的发生,那么我们如何解决这个问题呢?办法时我们必须在资源被变更与取用时的关键程序代码中实现“锁定”机制,代码如下:
import threading
def setTo1(data,lock):
while True:
lock.acquire()
try:
data['Justin']=1
if data['Justin'] !=1:
raise ValueError('setTo1 的数据不一致:{}'.format(str(data)))
finally:
lock.release()
def setTo2(data,lock):
while True:
lock.acquire()
try:
data['Justin']=2
if data['Justin']!=2:
raise ValueError('setTo2 的数据不一致:{}'.format(str(data)))
finally:
lock.release()
data={}
lock=threading.Lock()
a=threading.Thread(target=setTo1, args=(data,lock))
b=threading.Thread(target=setTo2, args=(data,lock))
a.start()
b.start()
threading.lock,也就是咱们所说的锁,它又两种状态,一种是锁定,一种是未锁定。在未锁定的状态下,可以使用acquire()方法来使程序进入锁定状态,此时如果另外调用acquire方法,那么调用的程序就会被阻断,直到其它地方调用了release()方法使lock解除锁定,特别注意,如果lock对象不是在锁定状态中,调用release()会引发RuntimeError的问题。
如果你还不太懂,我们拿上面的问题举例,当a将data['justin']的值改为1时,此时解释器移到b中的lock.acquire(),然而此时a占着lock还没有释放,b得不到lock会被阻断,所以只有当a执行完finally后的lock.release,b才可能响应,因此不会发生竞争条件,自然也不会报错。lock支持上下文管理协议,也就是(with...)因此上面的代码可以改为:
import threading
def setTo1(data,lock):
while True:
with lock:
data['Justin']=1
if data['Justin'] !=1:
raise ValueError('setTo1 的数据不一致:{}'.format(str(data)))
def setTo2(data,lock):
while True:
with lock:
data['Justin']=2
if data['Justin']!=2:
raise ValueError('setTo2 的数据不一致:{}'.format(str(data)))
data={}
lock=threading.Lock()
a=threading.Thread(target=setTo1, args=(data,lock))
b=threading.Thread(target=setTo2, args=(data,lock))
a.start()
b.start()
死锁
由于线程在无法获取锁时会造成阻断,因此不正确的使用lock有可能造成性能低下和死锁的行为,同样举个例子:
import threading
class Resource:
def __init__(self, name, resource):
self.name=name
self.resource = resource
self.lock = threading.Lock()
def action(self):
with self.lock:
self.resource+=1
return self.resource
def cooperate(self, other_res):
with self.lock:
other_res.action()
print('{}整合{}的资源'.format(self.name, other_res.name))
def cooperate(a,b):
for i in range(10):
a.cooperate(b)
res1 = Resource('resource 1',10)
res2 = Resource('resource 2',20)
t1 = threading.Thread(target=cooperate, args=(res1, res2))
t2 = threading.Thread(target=cooperate, args=(res2, res1))
t1.start()
t2.start()
有个死锁的原因是:t1在调用a.cooperate(b)时,此时a的lock是锁定状态,若正好解释器转到res2调用a.cooperate(b),那么此时res2的锁也处于锁定状态,那么现在问题来了,res1和res2的锁都处于锁定状态,谁都获取不到,那么程序自然就阻断了。