python c multithreading gil python-embedding

Incrustación de python en aplicaciones C multiproceso



multithreading gil (3)

Eventualmente lo descubrí.
Después

PyEval_InitThreads();

Necesitas llamar

PyEval_SaveThread();

Mientras que correctamente suelte el GIL para el hilo principal.

Estoy incorporando el intérprete de python en una aplicación de C multiproceso y estoy un poco confundido en cuanto a las API que debo usar para garantizar la seguridad de los subprocesos.

De lo que he recopilado, cuando incrusté python, depende del integrador cuidar del bloqueo de GIL antes de llamar a cualquier otra llamada de la API de Python C. Esto se hace con estas funciones:

gstate = PyGILState_Ensure(); // do some python api calls, run python scripts PyGILState_Release(gstate);

Pero esto solo no parece ser suficiente. Todavía tengo fallos aleatorios ya que no parece proporcionar la exclusión mutua para las API de Python.

Después de leer algunos documentos más también agregué:

PyEval_InitThreads();

justo después de la llamada a Py_IsInitialized() pero ahí es donde viene la parte confusa. Los documentos indican que esta función:

Inicializar y adquirir el bloqueo de intérprete global.

Esto sugiere que cuando esta función vuelve, se supone que la GIL está bloqueada y que debería desbloquearse de alguna manera. Pero en la práctica esto no parece ser necesario. Con esta línea en su lugar, mi multiproceso funcionó perfectamente y la exclusión mutua se mantuvo mediante las funciones PyGILState_Ensure/Release .
Cuando intenté agregar PyEval_ReleaseLock() después de PyEval_ReleaseLock() la aplicación se bloqueó rápidamente en una llamada posterior a PyImport_ExecCodeModule() .

Entonces, ¿qué me estoy perdiendo aquí?


Tener una aplicación C de múltiples subprocesos que intenta comunicarse desde varios subprocesos a varios subprocesos de Python de una sola instancia de CPython me parece arriesgado.

Siempre que solo un hilo C se comunique con Python, no debería preocuparse por el bloqueo, incluso si la aplicación Python es multihilo. Si necesita varios subprocesos de Python, puede configurar la aplicación de esta manera y hacer que varios subprocesos de C se comuniquen a través de una cola con ese único subproceso de C que los agrupa en varios subprocesos de Python.

Una alternativa que podría funcionar para usted es tener múltiples instancias de CPython una para cada subproceso C que lo necesite (por supuesto, la comunicación entre los programas de Python debe ser a través del programa C).

Otra alternativa podría ser el intérprete de Python sin pila. Eso elimina el GIL, pero no estoy seguro de que te encuentres con otros problemas que lo vinculen a varios subprocesos. stackless fue un reemplazo directo para mi aplicación C (de un solo hilo).


Tuve exactamente el mismo problema y ahora se resuelve utilizando PyEval_SaveThread() inmediatamente después de PyEval_InitThreads() , como sugieres anteriormente. Sin embargo, mi problema real fue que utilicé PyEval_InitThreads() después de PyInitialise() que provocó que PyGILState_Ensure() bloqueara cuando se llamaban desde diferentes subprocesos nativos posteriores. En resumen, esto es lo que hago ahora:

  1. Hay variable global:

    static int gil_init = 0;

  2. Desde un hilo principal, cargue la extensión C nativa e inicie el intérprete de Python:

    Py_Initialize()

  3. Desde muchos otros subprocesos, mi aplicación hace muchas llamadas a la API de Python / C al mismo tiempo:

    if (!gil_init) { gil_init = 1; PyEval_InitThreads(); PyEval_SaveThread(); } state = PyGILState_Ensure(); // Call Python/C API functions... PyGILState_Release(state);

  4. Desde el hilo principal detener el intérprete de Python

    Py_Finalize()

Todas las otras soluciones que probé causaron sigfaults de Python aleatorios o interbloqueo / bloqueo usando PyGILState_Ensure() .

La documentación de Python realmente debería ser más clara al respecto y, al menos, proporcionar un ejemplo para los casos de uso de extensión y incrustación.