Глобальная блокировка интерпретатора и доступ к данным (например, для массивов NumPy)

Я пишу расширение на языке Си для Python, которое должно освобождать глобальную блокировку интерпретатора во время работы с данными. Мне кажется, я достаточно хорошо понял механизм GIL, но остается один вопрос: Могу ли я получить доступ к данным в объекте Python, когда поток не владеет GIL? Например, я хочу прочитать данные из (большого) массива NumPy в функции C, при этом я все еще хочу позволить другим потокам заниматься другими делами на других ядрах процессора. Функция C должна

  • освободить GIL с помощью Py_BEGIN_ALLOW_THREADS
  • читать и работать с данными без использования функций Python
  • даже записывать данные в ранее созданные массивы NumPy
  • снова получить GIL с помощью Py_END_ALLOW_THREADS

Безопасно ли это? Конечно, другие потоки не должны изменять переменные, которые использует функция C. Но, возможно, существует один скрытый источник ошибок: может ли интерпретатор Python переместить объект, например, с помощью какой-нибудь сборки мусора, в то время как функция C работает над ним в отдельном потоке?

Чтобы проиллюстрировать вопрос на минимальном примере, рассмотрим (минимальный, но полный) код ниже. Скомпилируйте его (на Linux) с помощью

gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -fPIC -I/usr/lib/pymodules/python2.7/numpy/core/include -I/usr/include/python2.7 -c gilexample.c -o gilexample.o
gcc -pthread -shared gilexample.o -o gilexample.so

и протестируйте в Python с помощью

import gilexample
gilexample.sum([1,2,3])

Безопасен ли код между Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS? Он обращается к содержимому объекта Python, и я не хочу дублировать (возможно, большой) массив в памяти.

#include <Python.h>
#include <numpy/arrayobject.h>

// The relevant function
static PyObject * sum(PyObject * const self, PyObject * const args) {
  PyObject * X;
  PyArg_ParseTuple(args, "O", &X);
  PyObject const * const X_double = PyArray_FROM_OTF(X, NPY_DOUBLE, NPY_ALIGNED);
  npy_intp const size = PyArray_SIZE(X_double);
  double * const data = (double *) PyArray_DATA(X_double);
  double sum = 0;

  Py_BEGIN_ALLOW_THREADS // IS THIS SAFE?

  npy_intp i;
  for (i=0; i<size; i++)
    sum += data[i];

  Py_END_ALLOW_THREADS

  Py_DECREF(X_double);
  return PyFloat_FromDouble(sum);
}

// Python interface code
// List the C methods that this extension provides.
static PyMethodDef gilexampleMethods[] = {
  {"sum", sum, METH_VARARGS},
  {NULL, NULL, 0, NULL}     /* Sentinel - marks the end of this structure */
};

// Tell Python about these methods.
PyMODINIT_FUNC initgilexample(void)  {
  (void) Py_InitModule("gilexample", gilexampleMethods);
  import_array();  // Must be present for NumPy.
}
7
задан ivan_pozdeev 13 January 2015 в 10:11
поделиться