Bloqueo global de intérpretes y acceso a datos (por ejemplo, para matrices NumPy)

Estoy escribiendo una extensión en C para Python, que debería liberar el locking global de intérpretes mientras funciona con datos. Creo que he entendido bastante bien el mecanismo de la GIL, pero queda una pregunta: ¿Puedo acceder a los datos en un objeto de Python mientras el hilo no es propietario de la GIL? Por ejemplo, quiero leer datos de una matriz NumPy (grande) en la función C, mientras que todavía quiero permitir que otros subprocesos hagan otras cosas en los otros núcleos de la CPU. La función C debería

  • libera la GIL con Py_BEGIN_ALLOW_THREADS
  • lee y trabaja en los datos sin usar las funciones de Python
  • incluso escribir datos en arreglos NumPy construidos previamente
  • Py_END_ALLOW_THREADS adquirir la GIL con Py_END_ALLOW_THREADS

¿Es esto seguro? Por supuesto, otras hebras no deben cambiar las variables que utiliza la función C. Pero quizás haya una fuente oculta de errores: ¿podría el intérprete de Python mover un objeto, por ejemplo? por algún tipo de recolección de basura, mientras que la función C trabaja en ella en un hilo separado?

Para ilustrar la pregunta con un ejemplo mínimo, considere el código (mínimo pero completo) a continuación. Comstackrlo (en Linux) con

 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 

y probarlo en Python con

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

¿Es seguro el código entre Py_BEGIN_ALLOW_THREADS y Py_END_ALLOW_THREADS ? Accede al contenido de un objeto de Python, y no quiero duplicar la matriz (posiblemente grande) en la memoria.

 #include  #include  // 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. } 

¿Es esto seguro?

Estrictamente, no. Creo que deberías mover las llamadas a PyArray_SIZE y PyArray_DATA fuera del bloque sin GIL; Si lo haces, estarás operando solo en datos C También es posible que desee boost el recuento de referencia en el objeto antes de entrar en el bloque sin GIL y luego disminuirlo.

Después de sus ediciones, debería ser seguro. No te olvides de disminuir la cuenta de referencia después.

¿Puedo acceder a los datos en un objeto de Python mientras el hilo no es propietario de GIL?

No, no puedes.