threads pthread_join pthread_exit pthread_create pthread programacion por hilos compilar c multithreading

pthread_join - threads c#



¿Por qué este código es reentrante pero no es seguro para subprocesos? (6)

Solía ​​pensar que todas las funciones reentrantes son seguras para subprocesos. Pero leí la página Reentrancy en Wiki , publica un código que es "perfectamente reentrante, pero no seguro para subprocesos. Porque no garantiza que los datos globales estén en un estado constante durante la ejecución"

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?


El truco con este tipo de reentrada es que la ejecución de la primera llamada se detiene mientras se ejecuta la segunda llamada. Al igual que una llamada de subfunción. La primera llamada continúa después de la segunda llamada completamente terminada. Debido a que la función guarda el estado de t en la entrada y la restaura en la salida, 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 interrumpa 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 de ejecución definido en todos los subprocesos, solo dentro de un único subproceso. Entonces, el valor de t puede ser cambiado en cualquier momento por uno de los otros hilos.


Para dar una respuesta más genérica, la reentrada es solo 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 la función no reentrante es strtok

Por ejemplo, no es posible anidar 2 bucles de análisis 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 la segunda llamada strtok_r el estado del bucle externo de strtok (uno tiene que usar la variante reentrante strtok_r ).


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


Si tenía 2 instancias (cada una en un subproceso diferente) ejecutándola, una podría pisar los dedos de la otra: si una se interrumpía en el comentario de "interrupción de hardware" y otra se ejecutaba, podría cambiar t, de modo que volver al Primero tendría que producir resultados incorrectos.


Suponga que el hilo A y el hilo B. El hilo A tiene dos variables locales a = 5, b = 10 y el hilo B tiene dos variables locales p = 20, q = 30.

El hilo A llama: swap (& a, & b);

Llamadas del hilo B: 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 es local a la función dada. 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 no sea seguro. Di 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 trate de imaginar lo que habría pasado si las declaraciones 3 y 4 estuvieran en un orden diferente al de arriba. Entonces obtendríamos 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 subprocesos están en el mismo procesador. Entonces ninguna de las operaciones anteriores será simultánea. Acabo de mostrar intercalación en los pasos 3 y 4, ya que estos son los más importantes.


Voy a intentar ofrecer otro ejemplo (quizás menos ingenioso) de una función que es reentrante, pero no segura para subprocesos.

Aquí hay una implementación de las "Torres de Hanoi", utilizando una pila "temporal" global compartida:

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 porque deja el estado del búfer global tmp sin cambios cuando regresa (una advertencia: la restricción habitual de tener un tamaño creciente de discos en tmp puede violarse durante una llamada de reentrante). Sin embargo, hanoi() no es seguro para subprocesos

Aquí hay un ejemplo que es seguro para subprocesos y reentrante si el operador de incremento n++ es atómico:

int buf[MAX_SIZE]; /* global, shared buffer structure */ int n; /* global, shared counter */ int* alloc_int() { return &buf[n++]; }

Realmente podría usar esto como un asignador para celdas de un entero (no comprueba el desbordamiento; lo sé). Si n++ no es una operación atómica, dos subprocesos o dos llamadas de reentrante podrían fácilmente asignarse a la misma celda.