2012-02-02 10 views
15

Solía ​​pensar que todas las funciones de reentrada son seguras para subprocesos. Pero leí Reentrancy page in Wiki, que Mensajes código que es "perfectamente reentrada, pero no apta para subprocesos. Porque no asegura los datos mundial se encuentra en un estado coherente durante la ejecución"Por qué este código es reentrante pero no es seguro para subprocesos

int t; 

void swap(int *x, int *y) 
{ 
     int s; 

     s = t; // save global variable 
     t = *x; 
     *x = *y; 
     // hardware interrupt might invoke isr() here! 
     *y = t; 
     t = s; // restore global variable 
} 

void isr() 
{ 
     int x = 1, y = 2; 
     swap(&x, &y); 
} 

No entiendo su explicación . ¿Por qué esta función no es segura para subprocesos? ¿Es porque la variable global int t se cambiará durante la ejecución de los hilos?

+0

Este ejemplo es más que un poco artificial. Pero, re-enterant y thread-safe son conceptos ortogonales. –

+1

Posix tiene otra definición para reentrada "En POSIX.1c, una" función de reentrada "se define como una" función cuyo efecto, cuando es invocado por dos o más hilos, se garantiza que todos los subprocesos ejecutan la función uno tras otro en un orden indefinido, incluso si la ejecución real está intercalada ", lo que (bastante mal) ejemplo en wikipedia no cumpliría con – nos

+0

Me parece que este ejemplo no es reentrante: el' intercambio interrumpido() ' no intercambie los valores señalados por 'x' y' y' como se esperaba ('* y' podría establecerse en 2 cualquiera que sea el valor inicial de' * x'). – rom1v

Respuesta

4

Para dar una respuesta más genérica, la reentrada solo está en el nivel de función. Significa que una llamada de la función no cambia un estado en el que puede alterar el funcionamiento de una segunda llamada.

En el ejemplo dado, la variable global no se cambia entre dos llamadas de la función. Lo que sucede dentro de la función no tiene influencia en cada llamada de la función.

Un ejemplo de una función no reentrante es strtok

Es, por ejemplo, no es posible nido de 2 análisis de bucles con él:

/* To read a several lines of comma separated numbers */ 
char buff[WHATEVER], *p1, *p2; 

    p1 = strtok(buff, "\n"); 
    while(p1) { 
    p2 = strtok(p1, ","); 
    while(p2) { 
     atoi(p2); 
     p2 = strtok(NULL, ","); 
     } 
    } 
    p1 = strtok(NULL, "\n"); 
    } 

Esto no funciona, porque el estado del exterior El strtok loop es golpeado por la segunda llamada (uno tiene que usar la variante de reentrada strtok_r).

+3

La pregunta era pedir una explicación de cómo pueden ser reentradas las funciones no thread-safe. Esto no responde a la pregunta, simplemente proporciona un ejemplo de lo contrario. – Jed

0

Por lo tanto, la función se confunde con una variable global llamada t por alguna razón extraña. Si se llama a esta función desde dos subprocesos diferentes al mismo tiempo, es posible que obtenga resultados inesperados e incorrectos porque una instancia sobrescribirá el valor en t que escribió la otra instancia.

1

Si tenía 2 instancias (cada una en un hilo diferente) ejecutando, uno podría pisar el otro: si uno se interrumpió en el comentario "interrupción de hardware" y otro se ejecutó, podría cambiar t, de modo que volver al primero generaría resultados incorrectos.

4

El truco con este tipo de reentrada es que la ejecución de la primera llamada se detiene mientras se ejecuta la segunda llamada. Como una llamada de subfunción. La primera llamada continúa después de que la segunda llamada haya terminado por completo. Como la función guarda el estado de t en la entrada y la restaura al salir, nada ha cambiado para la primera llamada cuando continúa. Por lo tanto, siempre tiene un orden de ejecución definido y estricto, sin importar dónde se interrumpe exactamente la primera llamada.

Cuando esta función se ejecuta en varios subprocesos, todas las ejecuciones se realizan en paralelo, incluso en paralelo verdadero con una CPU multinúcleo. No hay un orden definido de ejecución en todos los hilos, solo dentro de un solo hilo. Entonces el valor de t puede ser cambiado en cualquier momento por uno de los otros hilos.

+0

Entonces la reentrada no incluye paralelo, por ejemplo, foo() {a(); b()}, cuando sucede la reentrada , solo podría ser un (ab) b, pero puede ' t ser a a b b qué multithread podría? –

+0

Básicamente sí. La reentrada como se muestra aquí es siempre (a2 b2), porque la llamada interna siempre se ejecuta por completo antes de que continúe la llamada externa. En el multihilo, depende del cronograma del hilo y podría ser también a1 a2 b1 b2. Hacer una reentrada de función no significa que sea seguro para subprocesos. Y a la inversa: hacerlo seguro para hilos no significa que sea reentrante. Ambos tienen que manejarse por separado, siempre es la pregunta si y cómo el estado (es decir, las variables) se comparte entre las llamadas y entre los hilos. – Secure

+0

Solo por la nota. Para mí, fue realmente difícil imaginar * "reingresar solo desde el mismo hilo" * situación. Como me temo que heisenbug, me preparé para implicar una ejecución paralela al enhebrar cosas. Y vi el término * reentrada * solo al enhebrar el texto relacionado. Creo que esto es lo mismo para las personas que comenzaron a programar después de que se hiciera común el uso de subprocesos preventivos. Pero irónicamente, este es el punto clave para entender el término. – Eonil

2

Voy a tratar de ofrecer otro ejemplo (tal vez menos artificial) de una función que es reentrante, pero no es segura para subprocesos.

Aquí es una implementación del "Torres de Hanoi", utilizando un "temp" compartida global apilar:

stack_t tmp; 

void hanoi_inner(stack_t src, stack_t dest, stack_t tmp, int n) 
{ 
    if (n == 1) move(src, dest) 
    else { 
    hanoi_inner(src, tmp, dest, n - 1); 
    move(src, dest); 
    hanoi_inner(tmp, dest, src, n - 1); 
    } 
} 

void hanoi(stack_t src, stack_t dest, int n) { hanoi_inner(src, dest, tmp, n); } 

La función hanoi() es reentrante, ya que deja el estado de la memoria intermedia mundial tmp sin cambios cuando se devoluciones (una advertencia: la restricción habitual de tener un tamaño creciente de discos en tmp puede ser violada durante una llamada de reentrada).) Sin embargo, hanoi() no es seguro para subprocesos.

Este es un ejemplo que es tanto seguro para subprocesos y de reentrada si el operador de incremento n++ es atómica:

int buf[MAX_SIZE]; /* global, shared buffer structure */ 
int n;    /* global, shared counter */ 

int* alloc_int() { return &buf[n++]; } 

Realmente podría utilizar esto como un asignador de células de un número entero (no comprueba desbordamiento; lo sé). Si n++ no es una operación atómica, dos hilos o dos llamadas reentrantes fácilmente podrían terminar asignados a la misma celda.

+1

Este solo es reentrante si n ++ es atómico (lo que generalmente no es). De lo contrario, ambas llamadas podrían devolver el mismo puntero. –

+0

@per Gracias - Voy a intentar cambiarlo para que sea reentrante sin recurrir al bloqueo ni a un n + atómico – gcbenison

3

Supongamos hilo A e hilo B. Tema A tiene dos variables locales a = 5, b = 10 y Tema B tiene dos variables locales p = 20, q = 30.

Tema A llama: swap (& a, & b);

Hilo B llamadas: swap (& p, & q);

Supongo que ambos subprocesos se ejecutan en diferentes núcleos y pertenecen al mismo proceso. La variable t es global e int x, int y son locales a la función tal como se da. La siguiente programación de subprocesos muestra cómo el valor de 't' puede variar según la programación de subprocesos y, por lo tanto, hacer que el subproceso de código sea inseguro. Digite global t = 100;

Thread A   Thread B 
1) int s;  int s; 
2) s = 100;  s = 100; 
3) t = 5;  no operation(nop); 
4) nop;   t = 20; // t is global so Thread A also sees the value as t = 20 
5) x = 10;  x = 30; 
6) y = 20;  y = 20; // Thread A exchange is wrong, Thread B exchange is OK 

Ahora trata de imaginar lo que habría ocurrido si las declaraciones 3 y 4 están en diferente orden superior. t obtendría el valor 5 y el intercambio en el hilo B sería incorrecto. La situación es aún más fácil si los dos hilos están en el mismo procesador. Entonces ninguna de las operaciones anteriores será simultánea. Acabo de mostrar el intercalado en los pasos 3 y 4, ya que estos son los más importantes.

Cuestiones relacionadas