Issue #3001: Add a C implementation of recursive locks which is used by

default when instantiating a `Threading.RLock` object. This makes
recursive locks as fast as regular non-recursive locks (previously,
they were slower by 10x to 15x).
This commit is contained in:
Antoine Pitrou 2009-11-10 18:46:01 +00:00
parent 0e31201848
commit 434736a1a6
4 changed files with 296 additions and 5 deletions

View File

@ -506,8 +506,11 @@ class ThreadingExceptionTests(BaseTestCase):
class LockTests(lock_tests.LockTests):
locktype = staticmethod(threading.Lock)
class RLockTests(lock_tests.RLockTests):
locktype = staticmethod(threading.RLock)
class PyRLockTests(lock_tests.RLockTests):
locktype = staticmethod(threading._PyRLock)
class CRLockTests(lock_tests.RLockTests):
locktype = staticmethod(threading._CRLock)
class EventTests(lock_tests.EventTests):
eventtype = staticmethod(threading.Event)
@ -527,7 +530,7 @@ class BoundedSemaphoreTests(lock_tests.BoundedSemaphoreTests):
def test_main():
test.support.run_unittest(LockTests, RLockTests, EventTests,
test.support.run_unittest(LockTests, PyRLockTests, CRLockTests, EventTests,
ConditionAsRLockTests, ConditionTests,
SemaphoreTests, BoundedSemaphoreTests,
ThreadTests,

View File

@ -27,6 +27,10 @@ _start_new_thread = _thread.start_new_thread
_allocate_lock = _thread.allocate_lock
_get_ident = _thread.get_ident
ThreadError = _thread.error
try:
_CRLock = _thread.RLock
except AttributeError:
_CRLock = None
del _thread
@ -79,8 +83,12 @@ def settrace(func):
Lock = _allocate_lock
def RLock(*args, **kwargs):
return _RLock(*args, **kwargs)
def RLock(verbose=None, *args, **kwargs):
if verbose is None:
verbose = _VERBOSE
if (__debug__ and verbose) or _CRLock is None:
return _PyRLock(verbose, *args, **kwargs)
return _CRLock(*args, **kwargs)
class _RLock(_Verbose):
@ -156,6 +164,8 @@ class _RLock(_Verbose):
def _is_owned(self):
return self._owner == _get_ident()
_PyRLock = _RLock
def Condition(*args, **kwargs):
return _Condition(*args, **kwargs)

View File

@ -123,6 +123,11 @@ C-API
Library
-------
- Issue #3001: Add a C implementation of recursive locks which is used by
default when instantiating a `threading.RLock` object. This makes
recursive locks as fast as regular non-recursive locks (previously,
they were slower by 10x to 15x).
- Issue #7282: Fix a memory leak when an RLock was used in a thread other
than those started through `threading.Thread` (for example, using
`_thread.start_new_thread()`).

View File

@ -155,6 +155,273 @@ static PyTypeObject Locktype = {
lock_methods, /*tp_methods*/
};
/* Recursive lock objects */
typedef struct {
PyObject_HEAD
PyThread_type_lock rlock_lock;
long rlock_owner;
unsigned long rlock_count;
PyObject *in_weakreflist;
} rlockobject;
static void
rlock_dealloc(rlockobject *self)
{
assert(self->rlock_lock);
if (self->in_weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *) self);
/* Unlock the lock so it's safe to free it */
if (self->rlock_count > 0)
PyThread_release_lock(self->rlock_lock);
PyThread_free_lock(self->rlock_lock);
Py_TYPE(self)->tp_free(self);
}
static PyObject *
rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds)
{
char *kwlist[] = {"blocking", NULL};
int blocking = 1;
long tid;
int r = 1;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i:acquire", kwlist,
&blocking))
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;
}
if (self->rlock_count > 0 ||
!PyThread_acquire_lock(self->rlock_lock, 0)) {
if (!blocking) {
Py_RETURN_FALSE;
}
Py_BEGIN_ALLOW_THREADS
r = PyThread_acquire_lock(self->rlock_lock, blocking);
Py_END_ALLOW_THREADS
}
if (r) {
assert(self->rlock_count == 0);
self->rlock_owner = tid;
self->rlock_count = 1;
}
return PyBool_FromLong(r);
}
PyDoc_STRVAR(rlock_acquire_doc,
"acquire(blocking=True) -> bool\n\
\n\
Lock the lock. `blocking` indicates whether we should wait\n\
for the lock to be available or not. If `blocking` is False\n\
and another thread holds the lock, the method will return False\n\
immediately. If `blocking` is True and another thread holds\n\
the lock, the method will wait for the lock to be released,\n\
take it and then return True.\n\
(note: the blocking operation is not interruptible.)\n\
\n\
In all other cases, the method will return True immediately.\n\
Precisely, if the current thread already holds the lock, its\n\
internal counter is simply incremented. If nobody holds the lock,\n\
the lock is taken and its internal counter initialized to 1.");
static PyObject *
rlock_release(rlockobject *self)
{
long tid = PyThread_get_thread_ident();
if (self->rlock_count == 0 || self->rlock_owner != tid) {
PyErr_SetString(PyExc_RuntimeError,
"cannot release un-acquired lock");
return NULL;
}
if (--self->rlock_count == 0) {
self->rlock_owner = 0;
PyThread_release_lock(self->rlock_lock);
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(rlock_release_doc,
"release()\n\
\n\
Release the lock, allowing another thread that is blocked waiting for\n\
the lock to acquire the lock. The lock must be in the locked state,\n\
and must be locked by the same thread that unlocks it; otherwise a\n\
`RuntimeError` is raised.\n\
\n\
Do note that if the lock was acquire()d several times in a row by the\n\
current thread, release() needs to be called as many times for the lock\n\
to be available for other threads.");
static PyObject *
rlock_acquire_restore(rlockobject *self, PyObject *arg)
{
long owner;
unsigned long count;
int r = 1;
if (!PyArg_ParseTuple(arg, "kl:_acquire_restore", &count, &owner))
return NULL;
if (!PyThread_acquire_lock(self->rlock_lock, 0)) {
Py_BEGIN_ALLOW_THREADS
r = PyThread_acquire_lock(self->rlock_lock, 1);
Py_END_ALLOW_THREADS
}
if (!r) {
PyErr_SetString(ThreadError, "couldn't acquire lock");
return NULL;
}
assert(self->rlock_count == 0);
self->rlock_owner = owner;
self->rlock_count = count;
Py_RETURN_NONE;
}
PyDoc_STRVAR(rlock_acquire_restore_doc,
"_acquire_restore(state) -> None\n\
\n\
For internal use by `threading.Condition`.");
static PyObject *
rlock_release_save(rlockobject *self)
{
long owner;
unsigned long count;
owner = self->rlock_owner;
count = self->rlock_count;
self->rlock_count = 0;
self->rlock_owner = 0;
PyThread_release_lock(self->rlock_lock);
return Py_BuildValue("kl", count, owner);
}
PyDoc_STRVAR(rlock_release_save_doc,
"_release_save() -> tuple\n\
\n\
For internal use by `threading.Condition`.");
static PyObject *
rlock_is_owned(rlockobject *self)
{
long tid = PyThread_get_thread_ident();
if (self->rlock_count > 0 && self->rlock_owner == tid) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
PyDoc_STRVAR(rlock_is_owned_doc,
"_is_owned() -> bool\n\
\n\
For internal use by `threading.Condition`.");
static PyObject *
rlock_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
rlockobject *self;
self = (rlockobject *) type->tp_alloc(type, 0);
if (self != NULL) {
self->rlock_lock = PyThread_allocate_lock();
if (self->rlock_lock == NULL) {
type->tp_free(self);
PyErr_SetString(ThreadError, "can't allocate lock");
return NULL;
}
self->in_weakreflist = NULL;
self->rlock_owner = 0;
self->rlock_count = 0;
}
return (PyObject *) self;
}
static PyObject *
rlock_repr(rlockobject *self)
{
return PyUnicode_FromFormat("<%s owner=%ld count=%lu>",
Py_TYPE(self)->tp_name, self->rlock_owner, self->rlock_count);
}
static PyMethodDef rlock_methods[] = {
{"acquire", (PyCFunction)rlock_acquire,
METH_VARARGS | METH_KEYWORDS, rlock_acquire_doc},
{"release", (PyCFunction)rlock_release,
METH_NOARGS, rlock_release_doc},
{"_is_owned", (PyCFunction)rlock_is_owned,
METH_NOARGS, rlock_is_owned_doc},
{"_acquire_restore", (PyCFunction)rlock_acquire_restore,
METH_O, rlock_acquire_restore_doc},
{"_release_save", (PyCFunction)rlock_release_save,
METH_NOARGS, rlock_release_save_doc},
{"__enter__", (PyCFunction)rlock_acquire,
METH_VARARGS | METH_KEYWORDS, rlock_acquire_doc},
{"__exit__", (PyCFunction)rlock_release,
METH_VARARGS, rlock_release_doc},
{NULL, NULL} /* sentinel */
};
static PyTypeObject RLocktype = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"_thread.RLock", /*tp_name*/
sizeof(rlockobject), /*tp_size*/
0, /*tp_itemsize*/
/* methods */
(destructor)rlock_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_reserved*/
(reprfunc)rlock_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
offsetof(rlockobject, in_weakreflist), /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
rlock_methods, /*tp_methods*/
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
rlock_new /* tp_new */
};
static lockobject *
newlockobject(void)
{
@ -752,6 +1019,8 @@ PyInit__thread(void)
return NULL;
if (PyType_Ready(&Locktype) < 0)
return NULL;
if (PyType_Ready(&RLocktype) < 0)
return NULL;
/* Create the module and add the functions */
m = PyModule_Create(&threadmodule);
@ -766,6 +1035,10 @@ PyInit__thread(void)
Py_INCREF(&Locktype);
PyDict_SetItemString(d, "LockType", (PyObject *)&Locktype);
Py_INCREF(&RLocktype);
if (PyModule_AddObject(m, "RLock", (PyObject *)&RLocktype) < 0)
return NULL;
Py_INCREF(&localtype);
if (PyModule_AddObject(m, "_local", (PyObject *)&localtype) < 0)
return NULL;