Diferencia entre revisiones de «Cerrojos»

De Wiki de Sistemas Operativos
Saltar a: navegación, buscar
(Ejemplo de implementación: cero ahora significa libre)
 
(No se muestran 8 ediciones intermedias de 5 usuarios)
Línea 5: Línea 5:
 
<source lang="c">
 
<source lang="c">
 
int cerrojo = 0;      /* Inicializamos la variable cerrojo. Esto sería sólo válido para
 
int cerrojo = 0;      /* Inicializamos la variable cerrojo. Esto sería sólo válido para
                         hilos, ya que los procesos no comparten espacio de memoria */
+
                         hilos, ya que los procesos no comparten espacio de memoria. La variable se inicializa FUERA de los hilos.
 +
                        Del while en adelante, ya es código perteneciente a los hilos en ejecución. */
  
 
while(cerrojo == 1);  // Protocolo de               
 
while(cerrojo == 1);  // Protocolo de               
Línea 15: Línea 16:
 
</source>
 
</source>
  
Sin embargo, esto es problemático: Supongamos que tenemos únicamente un proceso con dos hilos (Hx y Hy) y el planificador retire al hilo Hx justo antes de ejecutarse la instrucción ''cerrojo = 0;''. El hilo Hy ejecuta su código, entra en la sección crítica (Esto puede ocurrir ya que cerrojo sigue a 1) y el planificador le retira el uso del procesador dentro de ésta. A continuación, se le vuelve a asignar el procesador a Hx.
+
Sin embargo, esto es problemático: Supongamos que tenemos únicamente un proceso con dos hilos (Hx y Hy) y el planificador retira el hilo Hx justo antes de ejecutarse la instrucción ''cerrojo = 1;''. El hilo Hy ejecuta su código, entra en la sección crítica (Esto puede ocurrir ya que cerrojo sigue a 0) y el planificador le retira el uso del procesador dentro de ésta. A continuación, se le vuelve a asignar el procesador a Hx.
  
 
De esta forma, tenemos a dos hilos ejecutando código de la sección crítica. Para evitar este problema, debemos implementar los protocolos de entrada y salida de forma que se ejecuten en una sola instrucción, con lo cual o el planificador retira el uso del procesador antes de esta instrucción, o sino lo retira después de haberla ejecutado.
 
De esta forma, tenemos a dos hilos ejecutando código de la sección crítica. Para evitar este problema, debemos implementar los protocolos de entrada y salida de forma que se ejecuten en una sola instrucción, con lo cual o el planificador retira el uso del procesador antes de esta instrucción, o sino lo retira después de haberla ejecutado.
Línea 35: Línea 36:
 
</source>
 
</source>
  
El método lock comprueba mediante __sync_locktest_and_set(&cerrojo,1) el valor del cerrojo: si es 0, cambia el valor del cerrojo a 1 y devuelve 0, por tanto, se sale del while y entra en sección crítica; si es 1, devuelve 1 y continúa comprobando el valor, por tanto, no entra en situación crítica hasta que cambie el valor del cerrojo.
+
El método lock comprueba mediante __sync_locktest_and_set(&cerrojo,1) el valor del cerrojo: si es 0, cambia el valor del cerrojo a 1 y devuelve 0, por tanto, se sale del while y entra en sección crítica; si es 1, devuelve 1 y continúa comprobando el valor, por tanto, no entra en la sección crítica hasta que cambie el valor del cerrojo.
  
 
El método unlock, mediante _sync_lock_release(&cerrojo), cambia el valor del cerrojo a 0.
 
El método unlock, mediante _sync_lock_release(&cerrojo), cambia el valor del cerrojo a 0.
Línea 41: Línea 42:
 
* http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
 
* http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
 
* http://stackoverflow.com/questions/1383363/is-my-spin-lock-implementation-correct-and-optimal
 
* http://stackoverflow.com/questions/1383363/is-my-spin-lock-implementation-correct-and-optimal
 +
 +
 +
 +
5.4[[Ejercicio_de_concurrencia_de_procesos | Ejercicio de concurrencia de procesos]]
 +
 +
5.5[[Semáforos | Semáforos]]

Revisión actual del 17:30 2 abr 2020

Ejemplo de implementación

El siguiente código sería un posible ejemplo del uso de control de concurrencia mediante cerrojos:

int cerrojo = 0;      /* Inicializamos la variable cerrojo. Esto sería sólo válido para
                         hilos, ya que los procesos no comparten espacio de memoria. La variable se inicializa FUERA de los hilos.
                         Del while en adelante, ya es código perteneciente a los hilos en ejecución. */

while(cerrojo == 1);  // Protocolo de               
cerrojo = 1;          // entrada

...                   // Sección crítica

cerrojo = 0;          // Protocolo de salida

Sin embargo, esto es problemático: Supongamos que tenemos únicamente un proceso con dos hilos (Hx y Hy) y el planificador retira el hilo Hx justo antes de ejecutarse la instrucción cerrojo = 1;. El hilo Hy ejecuta su código, entra en la sección crítica (Esto puede ocurrir ya que cerrojo sigue a 0) y el planificador le retira el uso del procesador dentro de ésta. A continuación, se le vuelve a asignar el procesador a Hx.

De esta forma, tenemos a dos hilos ejecutando código de la sección crítica. Para evitar este problema, debemos implementar los protocolos de entrada y salida de forma que se ejecuten en una sola instrucción, con lo cual o el planificador retira el uso del procesador antes de esta instrucción, o sino lo retira después de haberla ejecutado.

Cerrojos mediante instrucciones atómicas

Podemos implementar cerrojos mediante instrucciones especiales de comprobación y puesta a 0 y 1.

int cerrojo = 0;

void lock() {
    while (__sync_lock_test_and_set(&cerrojo, 1));
}

void unlock() {
    __sync_lock_release(&cerrojo);
}

El método lock comprueba mediante __sync_locktest_and_set(&cerrojo,1) el valor del cerrojo: si es 0, cambia el valor del cerrojo a 1 y devuelve 0, por tanto, se sale del while y entra en sección crítica; si es 1, devuelve 1 y continúa comprobando el valor, por tanto, no entra en la sección crítica hasta que cambie el valor del cerrojo.

El método unlock, mediante _sync_lock_release(&cerrojo), cambia el valor del cerrojo a 0.


5.4 Ejercicio de concurrencia de procesos

5.5 Semáforos