2008-12-09 8 views

Respuesta

21

El código de reentrada no tiene estado en un solo punto. Puede invocar el código mientras algo se está ejecutando en el código. Si el código utiliza un estado global, una llamada puede sobreescribir el estado global, rompiendo el cómputo en la otra llamada.

El código de seguridad del hilo es código sin condiciones de carrera u otros problemas de simultaneidad. Una condición de carrera es cuando el orden en el que dos hilos hacen algo afecta el cálculo. Un problema de simultaneidad típico es cuando un cambio en una estructura de datos compartida puede completarse parcialmente y dejarse en un estado incoherente. Para evitar esto, debe usar mecanismos de control de simultaneidad como semáforos de mutexes para garantizar que nada más pueda acceder a la estructura de datos hasta que se complete la operación. Por ejemplo, un código puede ser no reentrante pero no susceptible a subprocesos si está protegido externamente por un mutex, pero aún tiene una estructura de datos global donde el estado debe ser consistente durante toda la duración de la llamada. En este caso, el mismo hilo podría iniciar una devolución de llamada en el procedimiento mientras está protegido por un mutex de grano grueso externo. Si la devolución de llamada se produjo desde dentro del procedimiento de no reentrada, la llamada podría dejar la estructura de datos en un estado que podría romper el cálculo desde el punto de vista de la persona que llama.

Un código puede ser reentrante pero no seguro para subprocesos si puede realizar un cambio no atómico en una estructura de datos compartida (y compartible) que podría interrumpirse en el medio de la actualización dejando la estructura de datos en un estado incosistente. En este caso, otro subproceso que acceda a la estructura de datos podría verse afectado por la estructura de datos medio modificada y bloquearse o realizar una operación que dañe los datos.

+3

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. –

+1

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

+0

@ConcernedOfTunbridgeWells, si un func usa el montón dentro, hay una gran posibilidad de que este func no vuelva a entrar. ¿Por qué? – Alcott

8

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.

Cuestiones relacionadas