版本:Python 3.7.0
系统:win10 64
上一篇文章,我们知道了threading模块中lock、lock.acquire()、lock.release()的实现原理:利用机器指令保证“上锁的过程”原子化,当锁被某个线程持有时,其他线程再来获取,就会处于忙等状态,那么reentrant lock 是如何保证:一个锁可以被一个线程访问多次,不会处于忙等状态?
一个锁可以被一个线程访问多次,不会处于死锁状态?
如果是一把互斥锁(threading.Lock()),那么下面的代码会发生堵塞:
import threading
lock = threading.Lock()
lock.acquire()
for i in range(10):
print('获取第二把锁')
lock.acquire()
print(f'test.......{i}')
lock.release()
lock.release()
同样的代码,如果换成(threading.RLock()),则不会发生堵塞:
import threading
lock = threading.RLock()
lock.acquire()
for i in range(10):
print('获取第二把锁')
lock.acquire()
print(f'test.......{i}')
lock.release()
lock.release()
可能我们大部分人都知道,RLock其实底层维护了一个互斥锁和一个计数器,那互斥锁和计数器到底是如何工作的?我们从RLock的实现源码一探究竟。
在threading内部,RLock实现方式有两种,一种是调用_thread模块下的RLock,它是用C语言写的,另外一种是用Python语言写的,不管哪种方式,其实现原理是一致的。
[threading]
def RLock(*args, **kwargs):
if _CRLock is None:
return _PyRLock(*args, **kwargs) # python语言实现的RLock
return _CRLock(*args, **kwargs) # _thread模块中RLock,C语言实现的
Python语言实现的RLock源码如下(C语言实现的RLock可见文末):
[threading]
class _RLock:
def __init__(self):
self._block = _allocate_lock() # _thread模块中定义一个锁对象的方法
self._owner = None # 用来标记哪个线程获取了锁
self._count = 0 # 计数器
def acquire(self, blocking=True, timeout=-1):
me = get_ident()
if self._owner == me:
self._count += 1
return 1
rc = self._block.acquire(blocking, timeout)
if rc:
self._owner = me
self._count = 1
return rc
def release(self):
if self._owner != get_ident():
raise RuntimeError("cannot release un-acquired lock")
self._count = count = self._count - 1
if not count:
self._owner = None
self._block.release()
acquire()方法的流程图:
结合acquire()方法的源码和流程图,可以清楚的得知,当一个线程通过acquire()获取一个锁时,首先会判断拥有锁的线程和调用acquire()的线程是否是同一个线程,如果是同一个线程,那么计数器+1,函数直接返回(return 1),如果两个线程不一致时,那么会通过调用底层锁(_allocate_lock())进行阻塞自己(也可能是获得锁)。
那到这里,文章开头所提的问题,就迎刃而解了:当某个线程内部多次调用可重入锁时,仅仅在第一次获取锁对象时调用了_thread模块中锁的acquire()方法,第二次,第三次...只是让计数器加1了而已;而当其他线程获取该锁时,因为调用了 _trhead模块中 _allocate_lock()方法阻塞了自己。
C语言实现的RLock:
[_threadmodule.c]
typedef struct {
PyObject_HEAD
PyThread_type_lock rlock_lock;
unsigned long rlock_owner;
unsigned long rlock_count;
PyObject *in_weakreflist;
} rlockobject;
static PyObject *
rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds)
{
_PyTime_t timeout;
unsigned long tid;
PyLockStatus r = PY_LOCK_ACQUIRED;
if (lock_acquire_parse_args(args, kwds, &timeout) < 0)
return NULL;
tid = PyThread_get_thread_ident();
if (self->rlock_count > 0 && tid == self->rlock_owner) {
unsigned long count = self->rlock_count + 1;
if (count <= self->rlock_count) {
PyErr_SetString(PyExc_OverflowError,
"Internal lock count overflowed");
return NULL;
}
self->rlock_count = count;
Py_RETURN_TRUE;
}
r = acquire_timed(self->rlock_lock, timeout);
if (r == PY_LOCK_ACQUIRED) {
assert(self->rlock_count == 0);
self->rlock_owner = tid;
self->rlock_count = 1;
}
else if (r == PY_LOCK_INTR) {
return NULL;
}
return PyBool_FromLong(r == PY_LOCK_ACQUIRED);
}
水平有限,欢迎批评指正,欢迎交流!