diff options
author | Antoine Pitrou <solipsis@pitrou.net> | 2009-11-10 18:46:01 (GMT) |
---|---|---|
committer | Antoine Pitrou <solipsis@pitrou.net> | 2009-11-10 18:46:01 (GMT) |
commit | 434736a1a621f785858e58efe682320178de7993 (patch) | |
tree | cbd08641338319c3c8d6c85d3680ee6c8dad279a /Modules/_threadmodule.c | |
parent | 0e31201848f45db86bea6bf5bd196e2db88fbfbe (diff) | |
download | cpython-434736a1a621f785858e58efe682320178de7993.zip cpython-434736a1a621f785858e58efe682320178de7993.tar.gz cpython-434736a1a621f785858e58efe682320178de7993.tar.bz2 |
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).
Diffstat (limited to 'Modules/_threadmodule.c')
-rw-r--r-- | Modules/_threadmodule.c | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index fe63c4b..5a3f92a 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -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; |