Получите блокировку перед чтением / записью атрибута на CPython C тип расширения - PullRequest
1 голос
/ 08 января 2020

Я пишу многопоточное расширение C для CPython. У меня есть POD, который сделан потокобезопасным с простым libuv rwlock . Я хотел бы обернуть это, чтобы он мог быть распределен и доступ к данным из Python через простые PyMemberDef. У меня вопрос, достаточно ли просто получить / снять блокировку в 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,
};

1 Ответ

0 голосов
/ 16 апреля 2020

В случае, если кто-то еще попытается сделать это: Да, это потокобезопасный для вашего C кода .

Не задаваемый вопрос: как насчет интерпретатора Python? Если только основной поток (интерпретатор, который вызвал ваше расширение) когда-либо вызывает / возвращает интерпретатору, вам не о чем беспокоиться.

Если, тем не менее, потоки появились в вашем C Расширение имеет возможность звонить в переводчик, необходимо учитывать GIL. Сначала отпустите GIL и получите состояние основного потока с помощью PyEval_SaveThread(). Затем используйте interp *, хранящийся в состоянии потока, чтобы раскрутить новые состояния потока с помощью PyThreadState_New(interp), по одному для каждого потока, который вы намереваетесь создать в C. Наконец, прежде чем перезвонить в Python интерпретатор, получите GIL с PyEval_RestoreThread(tstate), вызовите Python и отпустите его с PyEval_SaveThread(), когда вы закончите.

...