如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步,使用 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锁