Kaydet (Commit) 434736a1 authored tarafından Antoine Pitrou's avatar Antoine Pitrou

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).
üst 0e312018
......@@ -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,
......
......@@ -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)
......
......@@ -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()`).
......
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment