在 CPython C 扩展类型上的属性 read/write 之前获取锁
Acquire lock before attribute read/write on CPython C extension type
我正在为 CPython 编写多线程 C 扩展。我有 POD,它通过简单的 libuv rwlock 实现了线程安全。我想包装它,以便可以分配它并通过简单的 PyMemberDef
从 Python 访问数据。我的问题是,仅 acquire/release 锁定 getattro
和 setattro
是否足够,还是我遗漏了什么?我是 CPython API 的新手,所以请随意推荐完全不同的方法。
我正在尝试做的事情的简化示例:
#include <Python.h>
#include <uv.h>
typedef struct Pod_s {
uv_rwlock_t lock;
int number;
} Pod;
typedef struct PyPod_s {
PyObject_HEAD
Pod pod;
} PyPod;
static PyMemberDef[] PyPod_members = {
{"number", T_INT, offsetof(PyPod, pod) + offsetof(Pod, number)},
{0},
};
static PyObject *PyPod_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
PyPod *self;
self = (PyPod *) type->tp_alloc(type, 0);
if(self != NULL)
uv_rwlock_init(&self->pod.lock); // In real life this would be error-checked
return (PyObject *) self;
}
// Is this getattro/setattro safe?
static PyObject *PyPod_getattro(PyObject *self, PyObject *attr) {
PyPod *pypod = (PyPod *) self;
uv_rwlock_rdlock(&pypod->pod.lock);
PyObject *ret = PyObject_GenericGetAttr(self, attr);
uv_rwlock_rdunlock(&pypod->pod.lock);
return ret;
}
static int PyPod_setattro(PyObject *self, PyObject *attr, PyObject *value) {
PyPod *pypod = (PyPod *) self;
uv_rwlock_wrlock(&pypod->pod.lock);
int ret = PyObject_GenericSetAttr(self, attr, value);
uv_rwlock_wrunlock(&pypod->pod.lock);
return ret;
}
static PyTypeObject PyPodType = {
PyObject_HEAD_INIT(NULL)
.tp_name = "PodModule.Pod",
.tp_basicsize = sizeof(PyPod),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyPod_new,
.tp_members = PyPod_members,
.tp_getattro = PyPod_getattro,
.tp_setattro = PyPod_setattro,
};
以防其他人尝试这样做:是的,这对您的 C 代码是线程安全的。
这里没有提出的问题是 Python 解释器呢?如果只有主线程(调用您的扩展程序的解释器)曾经回调 into/returns 给解释器,那么您就没有什么可担心的。
但是,如果在您的 C 扩展中生成的线程能够调用解释器,则您需要考虑 GIL。先释放GIL,用PyEval_SaveThread()
获取主线程的状态。然后使用存储在线程状态中的 interp *
通过 PyThreadState_New(interp)
启动新的线程状态,一个用于您打算在 C 中创建的每个线程。最后,在回调到 Python 解释器之前,使用 PyEval_RestoreThread(tstate)
获取 GIL,调用 Python,完成后使用 PyEval_SaveThread()
释放它。
我正在为 CPython 编写多线程 C 扩展。我有 POD,它通过简单的 libuv rwlock 实现了线程安全。我想包装它,以便可以分配它并通过简单的 PyMemberDef
从 Python 访问数据。我的问题是,仅 acquire/release 锁定 getattro
和 setattro
是否足够,还是我遗漏了什么?我是 CPython API 的新手,所以请随意推荐完全不同的方法。
我正在尝试做的事情的简化示例:
#include <Python.h>
#include <uv.h>
typedef struct Pod_s {
uv_rwlock_t lock;
int number;
} Pod;
typedef struct PyPod_s {
PyObject_HEAD
Pod pod;
} PyPod;
static PyMemberDef[] PyPod_members = {
{"number", T_INT, offsetof(PyPod, pod) + offsetof(Pod, number)},
{0},
};
static PyObject *PyPod_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
PyPod *self;
self = (PyPod *) type->tp_alloc(type, 0);
if(self != NULL)
uv_rwlock_init(&self->pod.lock); // In real life this would be error-checked
return (PyObject *) self;
}
// Is this getattro/setattro safe?
static PyObject *PyPod_getattro(PyObject *self, PyObject *attr) {
PyPod *pypod = (PyPod *) self;
uv_rwlock_rdlock(&pypod->pod.lock);
PyObject *ret = PyObject_GenericGetAttr(self, attr);
uv_rwlock_rdunlock(&pypod->pod.lock);
return ret;
}
static int PyPod_setattro(PyObject *self, PyObject *attr, PyObject *value) {
PyPod *pypod = (PyPod *) self;
uv_rwlock_wrlock(&pypod->pod.lock);
int ret = PyObject_GenericSetAttr(self, attr, value);
uv_rwlock_wrunlock(&pypod->pod.lock);
return ret;
}
static PyTypeObject PyPodType = {
PyObject_HEAD_INIT(NULL)
.tp_name = "PodModule.Pod",
.tp_basicsize = sizeof(PyPod),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyPod_new,
.tp_members = PyPod_members,
.tp_getattro = PyPod_getattro,
.tp_setattro = PyPod_setattro,
};
以防其他人尝试这样做:是的,这对您的 C 代码是线程安全的。
这里没有提出的问题是 Python 解释器呢?如果只有主线程(调用您的扩展程序的解释器)曾经回调 into/returns 给解释器,那么您就没有什么可担心的。
但是,如果在您的 C 扩展中生成的线程能够调用解释器,则您需要考虑 GIL。先释放GIL,用PyEval_SaveThread()
获取主线程的状态。然后使用存储在线程状态中的 interp *
通过 PyThreadState_New(interp)
启动新的线程状态,一个用于您打算在 C 中创建的每个线程。最后,在回调到 Python 解释器之前,使用 PyEval_RestoreThread(tstate)
获取 GIL,调用 Python,完成后使用 PyEval_SaveThread()
释放它。