Pyhton并发与并行(4)——竞争,锁定与死锁

这部分的内容如果你学过操作系统的话会很快理解,如果没有学过操作系统可以先大致看看,我尽量讲清楚,如果还不清楚,可以跳过去看我下面的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的锁都处于锁定状态,谁都获取不到,那么程序自然就阻断了。

好了,本次文章写的有点多了,我尽量用语言表达清楚但是还可能出现阅读晦涩的现象,如果实在看不懂可以跳过,相信随着以后的知识积累和课程积累,再返回来看一遍问题肯定迎刃而解!

你可能感兴趣的:(Pyhton并发与并行(4)——竞争,锁定与死锁)