]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #3001: Add a C implementation of recursive locks which is used by
authorAntoine Pitrou <solipsis@pitrou.net>
Tue, 10 Nov 2009 18:46:01 +0000 (18:46 +0000)
committerAntoine Pitrou <solipsis@pitrou.net>
Tue, 10 Nov 2009 18:46:01 +0000 (18:46 +0000)
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).

Lib/test/test_threading.py
Lib/threading.py
Misc/NEWS
Modules/_threadmodule.c

index 41f57dc3120ed0e1d35cf326aab4371892475f22..330a2c5d562db93e04a35711e272e86e1aa58f10 100644 (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,
index 4bb0182e66f14730caa666825b33056c01b27edb..0e77060ed59fd2aceec6f82507851ab5ba1d1850 100644 (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)
index 8c0f0ecbd1f3377134ec526a02aab8c4cf1b16ea..978565a3aa5205449a2d7a456de63459d39ec65f 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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()`).
index fe63c4b6f23f25ea0464b02b4abe49ffc8dd76b5..5a3f92a543feb480d7fe5abdb8cf25028ae68a33 100644 (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;