python多线程锁Lock和RLock

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步,使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,分别用来获取和释放锁

Lock和RLock有什么区别呢?很多教程都只提到RLock可以多次acquire和release,举的例子就是类似这种:

import threading

rlock=threading.RLock()

def foo():
    rlock.acquire()
    rlock.acquire() # 可以多次获得锁
    print('multi-acquire')
    rlock.release()
    rlock.acquire() # acquire几次锁就要release 几次

t=threading.Thread(target=foo)
t.start()
t.join()

#输出
multi-acquire

看完了还是一头雾水,虽然知道了RLock可以多次获取释放,但有什么用呢?获取一次锁就行了为啥还要多次获取呢? 下面就举例说一下这个RLock可以多次获取锁的好处

假如在多线程环境下我们要对一个变量num进行加一操作,并且加一操作前我们需要检查这个变量是不是负值

import threading

lock=threading.Lock()
num=1

def check(): #检查变量是否小于零
    global num
    lock.acquire()
    if num<0:
        print('num<0')
    else:
        print('num>1')
    lock.release()

def add(): # 对变量进行+1操作
    global num
    lock.acquire()
    check() #加一操作前检查num的值是否小于零
    num += 1
    lock.release()

t=threading.Thread(target=add)
t.start()
t.join()

上面的这段代码会发生死锁情况,因为在add函数里先通过lock.acquire()获取了锁,然后调用check函数的时候,check函数里又在获取锁,这就造成死锁情况。这种情况也就是在一个加锁的操作里调用另一个加锁的方法,而且加的是同一把锁,就会发生死锁。解决方法很简单,就是把Lock换成RLock就行了

让我们在举一个实用点的例子
现在要使用递归方法计算一个数的累乘,并要求加锁实现计算过程中不会被其他线程干扰

from threading import Lock

lock = Lock()

def factorial(n):
    assert n > 0
    if n == 1:
        return 1
    
    with lock:       
        out = n * factorial(n - 1)

    return out

print(factorial(3)) #发生死锁,无法执行

上面递归的函数里使用with 语句简化了锁的acquire和release 写法。
在with语句里又递归调用了factorial函数本身,这就会发生再次请求锁的情况,所以第一次递归的时候就发生了死锁,解决的方法还是一样,把Lock换成RLock即可

上面的例子说明了多次获取锁的实际用途,其实RLock和Lock的区别还有另一点,就是:

Lock获取的锁可以被其他任何线程直接释放
RLock获取的锁只有获取这个锁的线程自己才能释放

看下面的例子:

import threading
import time

lock = threading.Lock()

def a():
    lock.acquire()
    time.sleep(3)
    lock.release()
    

def b():
    lock.release()
    print('lock released')


t1=threading.Thread(target=a)
t2=threading.Thread(target=b)

t1.start()
t2.start()

t1.join()
t2.join()

#输出:
lock released
Exception in thread Thread-1:
Traceback (most recent call last):
  File "D:\python3.6\lib\threading.py", line 916, in _bootstrap_inner
    self.run()
  File "D:\python3.6\lib\threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "C:/Users/test/Desktop/test.py", line 9, in a
    lock.release()
RuntimeError: release unlocked lock

t1线程在函数a里面获取了锁,然后sleep 3秒钟,这时候t2线程的b函数在没有获取锁的情况下直接就释放的锁,然后执行print('lock released')打印输出,等3秒后线程t1醒来后再试图释放一个已经被释放的锁的时候,就会报RuntimeError: release unlocked lock 错了,说明t2线程可以获取t1线程的lock锁

如果把上面的锁改成RLock,输出会变成这样

Exception in thread Thread-2:
Traceback (most recent call last):
  File "D:\python3.6\lib\threading.py", line 916, in _bootstrap_inner
    self.run()
  File "D:\python3.6\lib\threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "C:/Users/test/Desktop/test.py", line 13, in b
    lock.release()
RuntimeError: cannot release un-acquired lock

报错信息:无法释放一个没有获取到的锁, 说明t2 线程的b函数里,无法获取t1线程获取的rlock锁

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。