2.1.1. ¿Por qué es necesaria la sincronización?
Texto íntegro de la conversión. Las figuras y la disposición de las notas se pueden consultar en el PDF.
2.1.1. ¿Por qué es necesaria la sincronización?
Veamos por qué es necesaria la sincronización de los procesos con un caso concreto. Un ejemplo claro de variable compartida2 es el caso de una variable
contador, a la que denominaremos trabajo_pendiente, que se modificada por diferentes procesos (usuario y gestor); tenemos que:
a) Los diferentes procesos usuario que se están ejecutando de modo concurrente en el sistema la actualizan (la incrementan) cada vez que se envía un trabajo3 a la impresora. Así, esta variable compartida indica cuántos trabajos pendientes de impresión hay en el sistema.
b) El proceso gestor de la impresora envía los trabajos a imprimir y actualiza el valor del contador decrementándolo cada vez que finaliza la impresión de un trabajo.
Si la actualización de esta variable contador se efectúa sin ningún tipo de restricción, se pueden generar inconsistencias, es decir, resultados erróneos. En el caso de nuestro ejemplo, estos resultados erróneos podrían repercutir en el proceso de impresión, de manera que se podría dejar de imprimir algún trabajo o intentar imprimir algún trabajo que no existe.
Veamos cómo se pueden generar estas inconsistencias analizando nuestro ejemplo. Como ya hemos indicado antes, un proceso usuario a la hora de enviar un trabajo a imprimir incrementa la variable trabajo_pendiente (figura 1).
Notas y pies de figura de la fuente
(2)Suponemos que los procesos pueden compartir memoria. (3)Los trabajos que los procesos usuario envían a la impresora son ficheros.
Página 10
El proceso gestor de la impresora cada vez que se acaba de imprimir un trabajo decrementa la variable trabajo_pendiente (figura 2).
... trabajo_pendiente = trabajo_pendiente + 1
...
Figura 1. Fragmento de código del proceso de usuario
...
trabajo_pendiente = trabajo_pendiente - 1 ...
Figura 2. Fragmento de código del proceso gestor
Así pues, la variable compartida trabajo_pendiente indica cuántos trabajos están pendientes de ser enviados a la impresora.
El principal problema que se plantea con el código que acabamos de presentar, y en general con el código programado en cualquier lenguaje de alto nivel, es que durante la compilación del programa una instrucción de alto nivel se traduce en una o más instrucciones de lenguaje máquina.
Por ejemplo, en una máquina RISC (reduced instruction set computer) el código generado una vez se ha compilado el código de nuestro ejemplo podría ser el que indicamos a continuación: en el caso del código del proceso usuario (figura 3) y en el caso del código del proceso gestor de la impresora (figura 4).
LOAD R0,trabajo_pendiente ;Copiar el contenido de trabajo_pendiente en el registro R0
INC R0 STORE trabajo_pendiente,R0 ;Almacenar R0 en trabajo_pendiente
Figura 3. Código ensamblador correspondiente al fragmento de código del proceso usuario
LOAD R1,trabajo_pendiente ;Copiar el contenido de trabajo_pendiente en el registro R1 DEC R1 STORE trabajo_pendiente,R1 ;Almacenar R1 en trabajo_pendiente
Figura 4. Código ensamblador correspondiente al fragmento de código del proceso gestor Los detalles de la codificación no son importantes, lo que nos interesa advertir aquí es que durante la ejecución de la instrucción de alto nivel para incrementar/decrementar la variable trabajo_pendiente se realizan copias temporales de su contenido en registros del sistema4. Estas copias locales, como las que se llevan a cabo en los registros R0 y R1, pueden ser inconsistentes con el valor de la variable de la memoria en un determinado instante de la ejecución del proceso. Esta inconsistencia es la que puede llevar a las dos situaciones erróneas mencionadas:
Notas y pies de figura de la fuente
;Incrementar R0 ;Decrementar R1 (4)Los registros del sistema son registros de hardware y no han de ser necesariamente diferentes para cada copia temporal.
Página 11
- No impresión de un trabajo
Consideremos, por ejemplo, que la variable trabajo_pendiente tiene un valor inicial igual a 3 cuando el gestor de la impresora ejecuta las instrucciones en lenguaje máquina que se han especificado anteriormente de la manera siguiente (figura 5):
a) En primer lugar, hace una copia del valor de la variable en el registro R1. En este momento, el valor de R1 coincide con el valor de la variable trabajo_pendiente, 3.
b) A continuación, decrementa el valor del registro, que pasa a valer 2. Ahora el valor de la copia es diferente del valor de la variable trabajo_pendiente en la memoria, 3, y, por lo tanto, hay una inconsistencia.
Figura 5. El proceso gestor empieza a decrementar la variable trabajo_pendiente.
Si en este punto de la ejecución del código máquina pasa alguna cosa en el sistema5 que provoca que el proceso gestor deje de ejecutarse, el sistema queda inconsistente.
En este estado de inconsistencia, lo peor que puede suceder es que un proceso usuario intente enviar un trabajo a imprimir. Entonces, cuando el proceso quiere incrementar el valor de la variable trabajo_pendiente, sucede lo siguiente (figura 6):
a) Se hace una copia de la variable en el registro R0, y R0 vale 3, porque la variable trabajo_pendiente continúa teniendo el valor de 3, ya que el gestor no lo había actualizado.
b) Se incrementa el valor del registro R0, que pasa a valer 4.
c) Se actualiza la variable trabajo_pendiente, que pasa a valer 4.
Notas y pies de figura de la fuente
(5)Un cambio de contexto porque expira el quantum del proceso gestor, una interrupción de un dispositivo, etc.
Página 12
Figura 6. El proceso usuario incrementa la variable trabajo_pendiente.
Al cabo de un cierto tiempo, cuando el proceso gestor se vuelve a ejecutar (figura 7), como se había interrumpido justo cuando debía ejecutar la instrucción STORE, lo primero que ejecuta es STORE trabajo_pendiente, R1. El valor de R1 en el proceso gestor era 2, por lo tanto el valor de la variable trabajo_pendiente en la memoria se actualiza y pasa a ser 2.
Figura 7. El proceso gestor finaliza el decremento de la variable trabajo_pendiente.
Después de esta secuencia de operaciones, el valor de trabajo_pendiente no es correcto. Habíamos partido de un valor de 3, el proceso gestor lo ha decrementado una vez y un proceso usuario lo ha incrementado otra vez. Por lo tanto, el valor final de la variable debería ser 3 y, en cambio, lo que hemos conseguido es asignarle un valor igual a 2.
A efectos prácticos, en el sistema hay un trabajo que no se imprimirá.
- Intento de impresión de un trabajo no existente
El caso simétrico al anterior es aquél en el que el usuario inicia la operación de actualización de la variable y no la finaliza porque es interrumpido (figura 8 y figura 9). Este proceso implica que el valor final de la variable considerada, trabajo_pendiente, sea erróneo e igual a 4.
Página 13
Figura 8. El proceso usuario es interrumpido mientras incrementa la variable trabajo_pendiente y el proceso gestor decrementa la misma variable.
Figura 9. El proceso usuario finaliza el incremento de la variable trabajo_pendiente.
En este caso, el gestor de la impresora intentaría enviar a imprimir un trabajo que no existe.
Todos los procesos se habrían ejecutado correctamente si las operaciones de incremento y decremento que el programador escribe en el lenguaje de alto nivel se hubieran ejecutado de manera indivisible o atómica. La indivisibilidad a la hora de ejecutar estas operaciones nos asegura a efectos prácticos que cuando un proceso incrementa/decrementa la variable no deja el procesador hasta que no ha finalizado la actualización o, dicho de otra manera, una vez iniciada una operación de actualización de una variable compartida, ningún proceso puede iniciar otra operación de actualización de aquella variable hasta que no ha finalizado la primera.
Con este ejemplo se demuestra que la ejecución concurrente no sincronizada de procesos que utilizan variables compartidas puede dar lugar a errores irrecuperables. Este tipo de situaciones recibe el nombre de race conditions (condición de carrera) porque pueden generar un resultado erróneo en función de cómo hayan sido planificados los procesos y hay que erradicarlas de los programas.
Ver fragmento extraído sin normalizar
2.1.1. ¿Por qué es necesaria la sincronización?
Veamos por qué es necesaria la sincronización de los procesos con un caso (2)Suponemos que los procesos
concreto. Un ejemplo claro de variable compartida2 es el caso de una variable pueden compartir memoria.
contador, a la que denominaremos trabajo_pendiente, que se modificada
por diferentes procesos (usuario y gestor); tenemos que:
a) Los diferentes procesos usuario que se están ejecutando de modo concu- (3)Los trabajos que los procesos
rrente en el sistema la actualizan (la incrementan) cada vez que se envía un usuario envían a la impresora son
ficheros.
trabajo3 a la impresora. Así, esta variable compartida indica cuántos trabajos
pendientes de impresión hay en el sistema.
b) El proceso gestor de la impresora envía los trabajos a imprimir y actualiza
el valor del contador decrementándolo cada vez que finaliza la impresión de
un trabajo.
Si la actualización de esta variable contador se efectúa sin ningún tipo de res-
tricción, se pueden generar inconsistencias, es decir, resultados erróneos. En
el caso de nuestro ejemplo, estos resultados erróneos podrían repercutir en el
proceso de impresión, de manera que se podría dejar de imprimir algún tra-
bajo o intentar imprimir algún trabajo que no existe.
Veamos cómo se pueden generar estas inconsistencias analizando nuestro
ejemplo. Como ya hemos indicado antes, un procesousuario a la hora de
enviar un trabajo a imprimir incrementa la variable trabajo_pendiente (fi-
gura 1).
## Página 10
<!-- source-page: 10 -->
GNUFDL • PID_00214803 10 Comunicación y sincronización
El procesogestordelaimpresora cada vez que se acaba de imprimir un tra-
bajo decrementa la variable trabajo_pendiente (figura 2).
...
trabajo_pendiente = trabajo_pendiente + 1
...
Figura 1. Fragmento de código del proceso de usuario
...
trabajo_pendiente = trabajo_pendiente - 1
...
Figura 2. Fragmento de código del proceso gestor
Así pues, la variable compartida trabajo_pendiente indica cuántos trabajos
están pendientes de ser enviados a la impresora.
El principal problema que se plantea con el código que acabamos de presentar,
y en general con el código programado en cualquier lenguaje de alto nivel,
es que durante la compilación del programa una instrucción de alto nivel se
traduce en una o más instrucciones de lenguaje máquina.
Por ejemplo, en una máquina RISC (reduced instruction set computer) el código
generado una vez se ha compilado el código de nuestro ejemplo podría ser
el que indicamos a continuación: en el caso del códigodelprocesousuario
(figura 3) y en el caso del códigodelprocesogestordelaimpresora (figura 4).
LOAD R0,trabajo_pendiente ;Copiar el contenido de trabajo_pendiente en el registro R0
INC R0 ;Incrementar R0
STORE trabajo_pendiente,R0 ;Almacenar R0 en trabajo_pendiente
Figura 3. Código ensamblador correspondiente al fragmento de código del proceso usuario
LOAD R1,trabajo_pendiente ;Copiar el contenido de trabajo_pendiente en el registro R1
DEC R1 ;Decrementar R1
STORE trabajo_pendiente,R1 ;Almacenar R1 en trabajo_pendiente
Figura 4. Código ensamblador correspondiente al fragmento de código del proceso gestor
Los detalles de la codificación no son importantes, lo que nos interesa adver- (4)Los registros del sistema son re-
tir aquí es que durante la ejecución de la instrucción de alto nivel para incre- gistros de hardware y no han de
ser necesariamente diferentes para
mentar/decrementar la variable trabajo_pendiente se realizan copias tem- cada copia temporal.
porales de su contenido en registros del sistema4. Estas copias locales, como las
que se llevan a cabo en los registros R0 y R1, pueden ser inconsistentes con el
valor de la variable de la memoria en un determinado instante de la ejecución
del proceso. Esta inconsistencia es la que puede llevar a las dos situaciones
erróneas mencionadas:
## Página 11
<!-- source-page: 11 -->
GNUFDL • PID_00214803 11 Comunicación y sincronización
1)Noimpresióndeuntrabajo
Consideremos, por ejemplo, que la variable trabajo_pendiente tiene un
valor inicial igual a 3 cuando el gestor de la impresora ejecuta las instruccio-
nes en lenguaje máquina que se han especificado anteriormente de la manera
siguiente (figura 5):
a) En primer lugar, hace una copia del valor de la variable en el registro
R1. En este momento, el valor de R1 coincide con el valor de la variable
trabajo_pendiente, 3.
b) A continuación, decrementa el valor del registro, que pasa a valer 2. Ahora
el valor de la copia es diferente del valor de la variable trabajo_pendiente
en la memoria, 3, y, por lo tanto, hay una inconsistencia.
Figura 5. El proceso gestor empieza a decrementar la variable trabajo_pendiente.
Si en este punto de la ejecución del código máquina pasa alguna cosa en el (5)Un cambio de contexto porque
sistema5 que provoca que el proceso gestor deje de ejecutarse, el sistema queda expira el quantum del proceso ges-
tor, una interrupción de un dispo-
inconsistente. sitivo, etc.
En este estado de inconsistencia, lo peor que puede suceder es que un proce-
so usuario intente enviar un trabajo a imprimir. Entonces, cuando el proceso
quiere incrementar el valor de la variable trabajo_pendiente, sucede lo si-
guiente (figura 6):
a) Se hace una copia de la variable en el registro R0, y R0 vale 3, porque la va-
riable trabajo_pendiente continúa teniendo el valor de 3, ya que el gestor
no lo había actualizado.
b) Se incrementa el valor del registro R0, que pasa a valer 4.
c) Se actualiza la variable trabajo_pendiente, que pasa a valer 4.
## Página 12
<!-- source-page: 12 -->
GNUFDL • PID_00214803 12 Comunicación y sincronización
Figura 6. El proceso usuario incrementa la variable trabajo_pendiente.
Al cabo de un cierto tiempo, cuando el proceso gestor se vuelve a ejecutar (fi-
gura 7), como se había interrumpido justo cuando debía ejecutar la instruc-
ción STORE, lo primero que ejecuta es STORE trabajo_pendiente, R1. El
valor de R1 en el proceso gestor era 2, por lo tanto el valor de la variable
trabajo_pendiente en la memoria se actualiza y pasa a ser 2.
Figura 7. El proceso gestor finaliza el decremento de la variable trabajo_pendiente.
Después de esta secuencia de operaciones, el valor de trabajo_pendiente
no es correcto. Habíamos partido de un valor de 3, el proceso gestor lo ha
decrementado una vez y un proceso usuario lo ha incrementado otra vez. Por
lo tanto, el valor final de la variable debería ser 3 y, en cambio, lo que hemos
conseguido es asignarle un valor igual a 2.
A efectos prácticos, en el sistema hay un trabajo que no se imprimirá.
2)Intentodeimpresióndeuntrabajonoexistente
El caso simétrico al anterior es aquél en el que el usuario inicia la operación
de actualización de la variable y no la finaliza porque es interrumpido (figura
8 y figura 9). Este proceso implica que el valor final de la variable considerada,
trabajo_pendiente, sea erróneo e igual a 4.
## Página 13
<!-- source-page: 13 -->
GNUFDL • PID_00214803 13 Comunicación y sincronización
Figura 8. El proceso usuario es interrumpido mientras incrementa la variable trabajo_pendiente y el proceso gestor decrementa
la misma variable.
Figura 9. El proceso usuario finaliza el incremento de la variable trabajo_pendiente.
En este caso, el gestor de la impresora intentaría enviar a imprimir un trabajo
que no existe.
Todos los procesos se habrían ejecutado correctamente si las operaciones de
incremento y decremento que el programador escribe en el lenguaje de alto
nivel se hubieran ejecutado de manera indivisible o atómica. La indivisibili-
dad a la hora de ejecutar estas operaciones nos asegura a efectos prácticos que
cuando un proceso incrementa/decrementa la variable no deja el procesador
hasta que no ha finalizado la actualización o, dicho de otra manera, una vez
iniciada una operación de actualización de una variable compartida, ningún
proceso puede iniciar otra operación de actualización de aquella variable hasta
que no ha finalizado la primera.
Con este ejemplo se demuestra que la ejecución concurrente no sincronizada
de procesos que utilizan variables compartidas puede dar lugar a errores irre-
cuperables. Este tipo de situaciones recibe el nombre de race conditions (condi-
ción de carrera) porque pueden generar un resultado erróneo en función de
cómo hayan sido planificados los procesos y hay que erradicarlas de los pro-
gramas.