Uso de locking de subprocesos en la función de callback ctypes

Quiero usar un dll ctypes de una aplicación torcida. Minimo ejemplo fabricado aquí:

from ctypes import * from threading import Lock lock = Lock() dll = windll.LoadLibrary('mydll.dll') l = [1,2,3] def callback(): lock.acquire() l.pop() lock.release() return 0 C_CALLBACK = CFUNCTYPE(c_int) c_callback = C_CALLBACK(callback) # this is a non blocking function call starts a hardware task that fires a callback upon completion dll.registerCallback(c_callback) while(True): # in reality this block gets called from a twisted server application lock.acquire() l.append(l.pop() + 1) lock.release() 

La dll tiene una función ( dll.registerCallback ) que toma una función de callback de ctypes, inicia un evento de hardware y activa una callback cuando el hardware indica que la tarea de hardware está completa.

De la documentación de la API:

La función de callback se llama en un subproceso DAQmx.

En algún lugar de la web intentan explicar qué es un “subproceso DAQmx”:

… su callback se llamará y se ejecutará en un subproceso de controlador DAQmx y se ejecutará de forma asíncrona (no en el mismo subproceso) en relación con su progtwig.

La documentación completa se puede encontrar aquí . Cambié la firma de la función en mi ejemplo por simplicidad.

Así que supongo que podemos asumir con seguridad que la dll está generando un hilo.

¿Los lockings que tengo en vigencia garantizarán que la función de callback no intente acceder a la lista l cuando está en el medio de la operación pop en mainloop, y viceversa? ¿O este esquema solo funciona cuando usas subprocesos creados usando la biblioteca de threading ? ¿Cuál es la práctica recomendada aquí?

Lo primero que hace ctypes _CallPythonObject es llamar a PyGILState_Ensure() , que llamará a PyThreadState_New para crear un nuevo estado de hilo si es necesario. Más allá de eso, está el código de vainilla Python, por lo que su locking debería funcionar bien. Me funcionó en el siguiente ejemplo (Linux, Python 2.7.3):

 from ctypes import * import threading lock = threading.Lock() lib = CDLL('./tmp.so') callback_t = CFUNCTYPE(c_int) def f(): with lock: print threading.current_thread() return 21 callback = callback_t(f) lib.registerCallback(callback) 
 >>> lock.acquire() True >>> t = threading.Thread(target=lib.event) >>> t.start() >>> lock.locked() True >>> lock.release() >>> <_DummyThread(Dummy-2, started daemon -1230402704)> res: 21 

La salida de DummyThread es desde la impresión del hilo actual en la callback. Los hilos creados fuera de Python obtienen un nombre “ficticio”.

tmp.c:

 #include  #include  typedef int (*callback_t)(void); callback_t callback = NULL; void *doCallback(void *arg) { int res = callback(); printf("res: %d\n", res); pthread_exit(0); } int event(void) { pthread_t callback_thread; pthread_create(&callback_thread, NULL, doCallback, NULL); pthread_join(callback_thread, NULL); return 0; } int registerCallback(callback_t foo) { callback = foo; return 0; }