java - resueltos - ¿Cómo se sincronizan correctamente los hilos en el lado nativo de un entorno JNI?
juegos con hilos en java (1)
Pregunta breve
Estoy usando C ++ y Java en un solo proceso a través de JNI. Para el caso de uso en cuestión, tanto un hilo de C ++ como un hilo de Java acceden a los mismos datos, lo están haciendo en el lado de C ++, y quiero sincronizar correctamente el acceso.
Hasta ahora, casi toda mi sincronización de subprocesos JNI ha estado en el lado de Java, donde la respuesta es obvia: utilizar el paquete de simultaneidad de Java provisto y las funciones integradas del lenguaje de simultaneidad. Desafortunadamente, la respuesta no es tan obvia en el lado de C ++.
Lo que he probado hasta ahora
Intenté usar un mutex pthreads pensando que podría funcionar aunque no use pthreads para crear subprocesos, pero que de vez en cuando se atasque al intentar bloquear: mostraré un ejemplo de eso más adelante.
Detalles de la pregunta
En mi uso actual y específico, c ++ está buscando cambios provistos por Java en un temporizador de 1 segundo (no es lo que me gustaría, pero no estoy seguro de cómo lo haría impulsado por eventos dada la naturaleza del código de c ++ heredado ) El hilo de Java proporciona datos llamando a una función nativa y c ++ copia los datos en una estructura de c ++.
Este es el tipo de situación en el código (ocurre en 2 hilos, Thread1 y Thread2):
Ejemplo de código
Tenga en cuenta bastante SSCCE, ya que le faltan definiciones para TheData
y TheDataWrapper
, pero en realidad no importa lo que contienen. Supongamos que simplemente contienen un par de int
públicas si eso ayuda a su proceso de pensamiento (aunque, en mi caso, en realidad se trata de varias matrices de int y arrays de float).
C ++:
class objectA
{
void poll();
void supplyData(JNIEnv* jni, jobject jthis, jobject data);
TheDataWrapper cpp_data;
bool isUpdated;
void doStuff(TheDataWrapper* data);
};
// poll() happens on a c++ thread we will call Thread1
void objectA :: poll()
{
// Here, both isUpdated and cpp_data need synchronization
if(isUpdated)
{
do_stuff(&cpp_data);
isUpdated = false;
}
}
// supplyData happens on the Thread2, called as a native function from a java thread
void objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)
{
// some operation happens that copies the java data into a c++ equivalent
// in my specific case this happens to be copying ints/floats from java arrays to c++ arrays
// this needs to be synchronized
cpp_data.copyFrom(data);
isUpdated = true;
}
Java:
class ObjectB
{
// f() happens on a Java thread which we will call Thread2
public void f()
{
// for the general case it doesn''t really matter what the data is
TheData data = TheData.prepareData();
supplyData(data);
}
public native void supplyData(TheData data);
}
Lo que he probado hasta ahora Detalles
Cuando probé el bloqueo de pthread como a continuación, a veces la ejecución se bloquea en pthread_mutex_lock
. No debería haber un punto muerto en esta situación, pero solo para probar más ejecuté un escenario donde supplyData
no recibía ninguna llamada (no se proporcionaban datos), por lo que no debería haber habido un punto muerto, sin embargo, la primera llamada a la poll
será ocasionalmente cuelgan de todos modos. ¿Quizás usar una mutex de pthreads no es una buena idea en esta situación? O tal vez hice algo estúpido y lo sigo pasando por alto.
Hasta ahora, he intentado usar pthreads como a continuación:
Ejemplo de código
C ++:
class objectA
{
pthread_mutex_t dataMutex;
... // everything else mentioned before
}
// called on c++ thread
void objectA :: poll()
{
pthread_mutex_lock(&dataMutex);
... // all the poll stuff from before
pthread_mutex_unlock(&dataMutex);
}
// called on java thread
void objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)
{
pthread_mutex_lock(&dataMutex);
... // all the supplyData stuff from before
pthread_mutex_unlock(&dataMutex);
}
Otra opción que pensé pero no he hecho
También consideré usar JNI para volver a llamar a Java para solicitar un bloqueo usando el control de simultaneidad de Java. Eso debería funcionar, ya que cualquiera de los hilos debería bloquearse en el lado de Java según sea necesario. Sin embargo, dado que acceder a Java desde c ++ es excesivamente detallado, esperaba evitar pasar ese dolor de cabeza. Probablemente podría hacer una clase de C ++ que encapsula llamadas JNI en java para solicitar un bloqueo de java; eso simplificaría el código de C ++, aunque me pregunto acerca de la sobrecarga de cruzar de un lado a otro sobre JNI solo para bloqueos de subprocesos.
Parece que esto no es necesario, según el comentario de @Radiodef. Parece que JNI incluye las funciones MonitorEnter
/ MonitorExit
que ya manejan el bloqueo en el lado de C ++. Hay trampas al usar estos al mismo tiempo que los bloqueos convencionales en el lado de Java, así que lea aquí antes de usar. MonitorEnter
esto, y espero que MonitorEnter
/ MonitorExit
sea la respuesta y recomiendo a @Radiodef que responda el comentario.
Clausura
¿Cómo podría sincronizar esto correctamente? ¿Debería pthread_mutex_ (un) lock funcionar? Si no, ¿qué puedo usar para sincronizar entre el hilo de C ++ y el hilo de Java?
Aquí no se proporciona ningún código C ++ específico de JNI ya que el puente JNI está funcionando y puedo pasar datos de un lado a otro. La pregunta es específicamente sobre la sincronización adecuada entre los subprocesos de c ++ / java que de otra manera se comunican correctamente.
Como mencioné antes, preferiría evitar el esquema de votación, pero eso podría terminar siendo otra pregunta. El código heredado de c ++ muestra su parte de la interfaz de usuario en X / motivo, y si recuerdo correctamente, el hilo de c ++ anterior es la cadena de eventos para mostrar. El hilo java terminará siendo el hilo de envío del evento java una vez que la interfaz de usuario java para esta clase esté conectada, aunque por ahora el hilo java es un hilo de prueba automatizado; de cualquier manera, es un hilo de Java por separado.
El hilo de C ++ se adjunta a la JVM. De hecho, ese es el hilo de C ++ que creó la JVM, por lo que debe adjuntarse por defecto.
He tenido éxito al conectar otros elementos de la interfaz de usuario de Java en este programa, pero esta es la primera vez que C ++ necesita datos no atómicos de Java que deben sincronizarse. ¿Existe una forma correcta y generalmente aceptada de hacer esto?
Si ambos hilos están unidos a la JVM, puede acceder a la sincronización de la JNI a través de las JNIEnv
MonitorEnter(jobject)
y MonitorExit(jobject)
. Tal como suena, MonitorEnter
adquiere un bloqueo en el jobject
proporcionado, y MonitorExit
libera el bloqueo en el jobject
proporcionado.
NOTA: ¡ hay algunas dificultades que debe conocer! Observe el penúltimo párrafo de la MonitorEnter
de MonitorEnter
y el último párrafo de la MonitorExit
de MonitorExit
sobre la mezcla y la coincidencia de MonitorEnter
/ MonitorExit
con otros mecanismos similares que de otro modo podría considerar compatibles.
Mira aquí
MonitorEnter
jint MonitorEnter (JNIEnv * env, jobject obj);
Ingresa al monitor asociado con el objeto Java subyacente al que se refiere obj. Ingresa al monitor asociado con el objeto al que se refiere obj. La referencia obj no debe ser NULL. Cada objeto Java tiene un monitor asociado. Si el hilo actual ya posee el monitor asociado con obj, incrementa un contador en el monitor que indica el número de veces que este hilo ha entrado en el monitor. Si el monitor asociado con obj no es propiedad de ningún subproceso, el subproceso actual se convierte en el propietario del monitor, estableciendo el recuento de entrada de este monitor en 1. Si otro subproceso ya posee el monitor asociado con obj, el subproceso actual espera hasta que el monitor se lanza, luego intenta de nuevo para ganar la propiedad.
Un monitor ingresado a través de una llamada a la función MonitorEnter JNI no puede salir usando la instrucción de máquina virtual monitorexit Java o un retorno de método sincronizado. Una llamada de función MonitorEnter JNI y una instrucción de máquina virtual Java de monitorenter pueden competir para ingresar al monitor asociado con el mismo objeto.
Para evitar interbloqueos, un monitor ingresado a través de una llamada a la función MonitorEnter JNI debe salir utilizando la llamada MonitorExit JNI, a menos que la llamada DetachCurrentThread se use para liberar implícitamente monitores JNI.
ENLACE :
Índice 217 en la tabla de funciones de la interfaz JNIEnv.
PARÁMETROS :
env: el puntero de la interfaz JNI.
obj: un objeto normal de Java u objeto de clase.
DEVOLUCIONES :
Devuelve "0" en caso de éxito; devuelve un valor negativo en la falla.
y
MonitorExit
jint MonitorExit (JNIEnv * env, jobject obj);
El hilo actual debe ser el propietario del monitor asociado con el objeto Java subyacente al que se refiere obj. El hilo disminuye el contador indicando la cantidad de veces que ha entrado en este monitor. Si el valor del contador se vuelve cero, el hilo actual libera el monitor.
El código nativo no debe usar MonitorExit para salir de un monitor ingresado a través de un método sincronizado o una instrucción de máquina virtual Java monitorenter.
ENLACE :
Índice 218 en la tabla de funciones de la interfaz JNIEnv.
PARÁMETROS :
env: el puntero de la interfaz JNI.
obj: un objeto normal de Java u objeto de clase.
DEVOLUCIONES :
Devuelve "0" en caso de éxito; devuelve un valor negativo en la falla.
EXCEPCIONES :
IllegalMonitorStateException: si el hilo actual no posee el monitor.
Por lo tanto, el código C ++ en la pregunta que intentó utilizar pthreads se debe cambiar de la siguiente manera (el código asume que el puntero JNIEnv*
se adquirió de alguna manera de antemano con la moda JNI típica):
class objectA
{
jobject dataMutex;
... // everything else mentioned before
}
// called on c++ thread
void objectA :: poll()
{
// You will need to aquire jniEnv pointer somehow just as usual for JNI
jniEnv->MonitorEnter(dataMutex);
... // all the poll stuff from before
jniEnv->MonitorExit(dataMutex);
}
// called on java thread
void objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)
{
// You will need to aquire jniEnv pointer somehow just as usual for JNI
jniEnv->MonitorEnter(dataMutex);
... // all the supplyData stuff from before
jniEnv->MonitorExit(dataMutex);
}
Felicitaciones a @Radiodef que proporcionó la respuesta. Lamentablemente fue como un comentario. Esperé hasta la tarde del día siguiente para darle tiempo a Radiodef para que respondiera, así que ahora lo estoy haciendo. Gracias Radiodef por proporcionarme el empujón que necesitaba para arreglar esto.