2012-01-11 5 views
7

Estoy escribiendo una extensión C para Python, que debería liberar el bloqueo de Intérprete global mientras opera en datos. Creo que he entendido bastante bien el mecanismo del GIL, pero queda una pregunta: ¿puedo acceder a los datos en un objeto de Python mientras el hilo no posee el GIL? Por ejemplo, quiero leer datos de una matriz (grande) de NumPy en la función C, mientras que aún quiero permitir que otros hilos hagan otras cosas en los otros núcleos de la CPU. La función C debeIntérprete global Bloqueo y acceso a datos (por ejemplo, para matrices NumPy)

  • liberación del GIL con Py_BEGIN_ALLOW_THREADS
  • lectura y el trabajo en los datos sin necesidad de utilizar las funciones de Python
  • incluso escribir datos en matrices NumPy previamente construidos
  • readquirir la GIL con Py_END_ALLOW_THREADS

¿Esto es seguro? Por supuesto, se supone que otros hilos no cambian las variables que utiliza la función C. Pero tal vez haya una fuente oculta de errores: ¿podría el intérprete de Python mover un objeto, por ej. por algún tipo de recolección de basura, mientras que la función C funciona en un hilo separado?

Para ilustrar la pregunta con un ejemplo mínimo, considere el código (mínimo pero completo) a continuación. Compilarlo (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 el código entre Py_BEGIN_ALLOW_THREADS y Py_END_ALLOW_THREADS seguro? Accede al contenido de un objeto de Python, y no quiero duplicar la matriz (posiblemente grande) en la memoria.

#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. 
} 
+2

Hice cosas como esta en el pasado, y encontré que la forma más fácil de hacer esto es usar 'ctypes' para llamar a sus funciones en C. Dé a sus funciones C una interfaz C pura sin ninguna referencia a Python o NumPy, y escriba envolturas triviales en Python que acepten matrices NumPy y las traduzcan a los parámetros C apropiados. Di un ejemplo sobre cómo hacer esto en [esta respuesta] (http://stackoverflow.com/questions/5862915/passing-numpy-arrays-to-ac-function-for-input-and-output/5868051#5868051) –

+0

@Sven: ¿Sabe si 'ctypes' hace una copia de trabajo de la matriz en la memoria? (1) Sí. En este caso, no lo quiero, ya que estoy tratando con grandes matrices de entrada. (2) No.Entonces mi pregunta de si puede levantar el GIL sigue siendo válida. Sin embargo, en el caso (2), el comportamiento de 'ctypes' sería una pista de que levantar el GIL probablemente no sea problemático, también en el código que no usa ctypes. ¿Alguien sabe si (1) o (2) tiene? – Daniel

+5

No, 'ctypes' no hace una copia de las matrices. Y libera el GIL por usted, por lo que no tiene que preocuparse por eso. La ventaja de usar 'ctypes' es la simplicidad: tiene que extraer toda la metainformación necesaria de la matriz NumPy mientras aún está en Python, y la GIL se lanza en el momento justo. Usé este enfoque para acceder concurrentemente a los datos en matrices NumPy desde múltiples hilos. (Tenga en cuenta que el acceso de escritura simultáneo a la misma memoria nunca se guarda.) –

Respuesta

4

¿Puedo acceder a los datos en un objeto de Python, mientras que el hilo no es dueño del GIL?

No, no puedes.

+0

¿Eso significa que siempre tengo que copiar el contenido de una matriz NumPy, incluso si solo quiero leer los datos en una secuencia? ¡Espero que haya una forma de evitarlo! ¿Cualquier sugerencia? – Daniel

6

¿Es esto seguro?

Estrictamente, no. Creo que debería mover las llamadas al PyArray_SIZE y PyArray_DATA fuera del bloque GIL-less; si haces eso, estarás operando solo en C datos. También es posible que desee aumentar el recuento de referencias en el objeto antes de entrar en el bloque sin GIL y disminuirlo después.

Después de sus ediciones, debería ser seguro. No olvide disminuir el conteo de referencia después.

+0

Gracias por el comentario sobre 'PyArray_SIZE' y' PyArray_DATA'. Eso fue un error. Edité mi pregunta y moví los comandos fuera del bloque donde se lanza el GIL. – Daniel

+0

Ahora: ¿es seguro el código en su versión revisada? ¿Puede explicar por qué el incremento del conteo de referencia podría cambiar las cosas? – Daniel

+0

@Daniel: puede ser necesario el recuento de referencias (aunque no estoy del todo seguro en este caso) porque ningún otro subproceso debe desasignar la matriz. –

Cuestiones relacionadas