Ese artículo dice:
"Una función puede ser de reentrada, hilo de seguridad, ambos o ninguno."
También dice:
"funciones no reentrantes son hilos inseguros".
Puedo ver cómo esto puede causar confusión. Significan que las funciones estándar documentadas como no necesarias para ser reentrantes tampoco son necesarias para ser seguras para subprocesos, lo cual es cierto para las bibliotecas POSIX iirc (y POSIX declara que también es cierto para las bibliotecas ANSI/ISO, teniendo ISO ningún concepto de hilos y, por lo tanto, ningún concepto de seguridad de hilos). En otras palabras, "si una función dice que no es reentrante, entonces está diciendo que también es inseguro para el hilo". Esa no es una necesidad lógica, es solo una convención.
Aquí hay un pseudocódigo que es seguro para subprocesos (bueno, hay muchas oportunidades para que las devoluciones de llamadas creen puntos muertos debido a la inversión de bloqueo, pero supongamos que la documentación contiene suficiente información para evitarlo) pero no reentrantes .Se supone que incrementar el contador global, y realizar la devolución de llamada:
take_global_lock();
int i = get_global_counter();
do_callback(i);
set_global_counter(i+1);
release_global_lock();
Si la devolución de llamada llama a esta rutina de nuevo, lo que resulta en otro de devolución de llamada, a continuación, tanto a nivel de devolución de llamada tendrán el mismo parámetro (que podría estar bien, dependiendo de la API), pero el contador solo se incrementará una vez (lo que es casi seguro que no es la API que desea, por lo que debería prohibirse).
Eso supone que el bloqueo es recursivo, por supuesto. Si el bloqueo no es recursivo, entonces, por supuesto, el código no es reentrante de todos modos, ya que tomar el bloqueo la segunda vez no funcionará.
He aquí algunos pseudo-código que es "re-entrante débilmente", pero no apta para subprocesos:
int i = get_global_counter();
do_callback(i);
set_global_counter(get_global_counter()+1);
Ahora que está bien llamar a la función de la devolución de llamada, pero no es seguro llamar a la función concurrente de diferentes hilos. Tampoco es seguro llamarlo desde un manejador de señal, porque la reentrada de un manejador de señal también podría romper el conteo si la señal sucediera en el momento correcto. Entonces el código no entra por la definición correcta.
Aquí hay un código que podría decirse que es completamente reentrante (excepto que creo que el estándar distingue entre reentrantes y 'no interrumpible por señales', y no estoy seguro de dónde se produce), pero todavía no es thread- segura:
int i = get_global_counter();
do_callback(i);
disable_signals(); // and any other kind of interrupts on your system
set_global_counter(get_global_counter()+1);
restore_signal_state();
en una aplicación de un solo subproceso, esto está muy bien, en el supuesto de que el sistema operativo soporta la desactivación de todo lo que tiene que ser desactivado. Impide que la reentrada se produzca en el punto crítico. Dependiendo de cómo se deshabiliten las señales, puede ser seguro llamar desde un manejador de señal, aunque en este ejemplo particular todavía existe la cuestión de que el parámetro pasado a la devolución de llamada sea el mismo para llamadas separadas. Sin embargo, aún puede salir mal con múltiples subprocesos.
En la práctica, no seguro para subprocesos a menudo implica no reentrante, ya que (informalmente) cualquier cosa que pueda salir mal debido a que el programador interrumpe y la función llamada nuevamente desde otro subproceso, también puede ir mal si el hilo es interrumpido por una señal, y la función es llamada nuevamente desde el manejador de señal. Pero luego la "solución" para evitar las señales (deshabilitarlas) es diferente de la "solución" para evitar la concurrencia (bloqueos, por lo general). Esta es, en el mejor de los casos, una regla empírica.
Tenga en cuenta que he implícito globales aquí, pero se aplicarán exactamente las mismas consideraciones si la función tomó como parámetro un puntero al contador y el bloqueo. Es solo que los diversos casos serían inseguros o no reentrantes cuando se invocan con el mismo parámetro, en lugar de cuando se invocan.
Su segundo ejemplo no me suena reentrante. Si el cambio puede interrumpirse dejando un estado incoherente, y se produce una señal en ese punto, y el controlador de esa señal llama a la función, entonces normalmente se dispara. Es un problema de reentrada, no un problema de seguridad de subprocesos. –
Tiene usted razón, como dice a continuación, también debería desactivar las señales para que el último ejemplo sea efectivamente reentrante. – ConcernedOfTunbridgeWells
@ConcernedOfTunbridgeWells, si un func usa el montón dentro, hay una gran posibilidad de que este func no vuelva a entrar. ¿Por qué? – Alcott