CONTROL DE CONCURRENCIA –CANDADOS-

(Héctor Duque, Grupo de Procesamiento Paralelo, UniAndes, Bogotá, Colombia, Febrero 3/98)

El Sr. Duque nos expone algunas contemplaciones sobre el tema de la concurrencia y nos explica algunos ejemplos con lenguajes de programacion, por llegar a estos limite no toca el tema muy profundamente

Lisandro Echeverría Villalobos.

 

1. INTRODUCCION

 

La consistencia e integridad de información residente –especialmente- en memoria secundaria, se ve seriamente afectada por el mal manejo de concurrencia generada en ambientes multi-usuario/multi-tarea; requerimientos simultáneos a un mismo dato, originados por procesos diferentes o instancias diferentes de una misma transacción, podrían producir valores no esperados por los datos.

 

Una de las técnicas desarrolladas para garantizar la integridad de este tipo de información es denominada como Control de Concurrencia por Candados; su manejo será tratado en este trabajo y se mostrará en tres (3) niveles básicos:

 

  1. Archivos planos.
  2. Bases de datos centralizadas.
  3. Bases de datos distribuidas.

 

En primera instancia se estudia la forma como un lenguaje de relativo bajo nivel –C- maneja este problema sobre los datos presentados en su –también- más bajo nivel: archivos planos representados como secuencia de caracteres. Se entiende que este tipo de representación de datos es la base sobre la cual un DBMS – Sistema Manejados de Bases de Datos- genera niveles superiores de representación de información (Base de Datos – DB) y por tanto enfrenta problemas más complejos en el manejo de la concurrencia; es esta la razón por la cual a ese nivel solo se estudia el manejo conceptual del problema y no su manejo físico –bajo nivel-. Finalmente se hace una aproximación al problema de concurrencia en Bases de Datos Distribuidas basándose en los avances logrados para Bases de Datos Centralizadas.

 

 

2. CONTROL DE ARCHIVOS DESDE C.

 

El lenguaje de programación C maneja los datos en disco como secuencias de caracteres asociadas con un nombre, lo cual corresponde a la representación de más bajo nivel para un archivo de datos. C suministra funciones –primitivas- para abrir, leer o escribir sobre archivos de datos, chequeos sobre la existencia del archivo o sobre el permiso para usarlos; sin embargo, el caso de requerimientos simultáneos a un mismo dato exige la participación de bloqueos sobre estos. El caso planteado hace imprescindible que el acceso a los datos sea manejado en forma atómica y sincronizada a fin de garantizar que estos sean íntegros, es decir, que un dato sea consistente para diferentes procesos que intenten leerlo y/o actualizarlo en momentos de tiempo que puedan considerarse como simultáneos.

 

Puede imaginarse una situación en que dos (2) procesos accesan una archivo de datos común, uno para leer un registro en especial y otro para actualizarlo; la mala sincronización de los accesos y la no atomicidad de estos, podrían producir que el proceso lector vea información parcialmente actualizada, lo cual ocurriría cuándo el proceso que actualiza interrumpe al proceso lector en mitad de una transacción de consulta sobre el archivo.

 

Para controlar este tipo de situaciones se implementan candados de lectura y candados de escritura, siendo los primeros, aquellos que permiten la creación de varios candados sobre una misma región del archivo, pero impiden que otro proceso escriba sobre ellos. El candado de escritura tiene características de unicidad e impide que cualquier otro proceso accese la misma área del archivo con objetivos tanto de lectura como de escritura.

 

2.1. Función fcntl.

 

La función fcntl proporciona control sobre archivos abiertos y permite el manejo de bloqueos sobre la totalidad o parte de un archivo de datos, teniendo especial importancia el caso en que existe simultaneidad de procesos requiriendo el mismo archivo – y probablemente el mismo dato-. La llamada fcntl permite la implementación de candados de lectura y/o escritura usando los valores asociados F_SETLK y F_SETLKW .

 

El valor F_SETLK permite definir candados de lectura (F_RDLCK) o de escritura (F_WRLCK) sobre el archivo, o permite eliminar uno existente (F_UNLCK). Si no se puede fijar el candado, la función termina y se obtiene un valor de –1 como respuesta. El valor F_SETLKW solo se diferencia de la anterior en que el proceso se dormirá hasta que el bloqueo le sea permitido.

 

Teniendo en cuenta que los candados fijados sobre un proceso se borran cuando este termina, y que los candados no se heredan de procesos padre a procesos hijos –fork-, se muestra a continuación la implementación C de un programa que:

 

  1. Lee un número de un archivo, lo incrementa, lo imprime en pantalla y lo escribe de nuevo en el archivo.
  2. Puede estar ejecutándose varias veces en forma simultánea sobre el mismo archivo.
  3. Ser puede ejecutar con opción de bloqueo (-b) o sin bloqueo, a discreción del usuario.

 

 

#include <stdio.h>

#include <string.h>

#include <fcntl.h>

#include <unistd.h>

 

#ifndef SEEK_SET

#define SEEK_SET 0

#endif

 

#define EQ(str1,str2)(strcmp(str1,str2) == 0)

#define ARCHIVO "tmp"

 

sin_bloqueo () {}

 

con_bloqueo (fd,orden)

int fd, orden;

{

struck flock candado

 

candado.l_type = orden;

candado.l.whence = SEEK_SET;

candado.l.start = 0;

candado.l.len = 0;

 

/* se intenta bloquear, de lo contrario el proceso dormirá */

if (fcntl (fd, F_SETLKW, &candado) == -1)

{

perror ("fcntl");

exit (-1);

}

}

 

main (argc, argv)

int argc;

char *argv[];

{

int fd

int numero, i, j;

int (*bloquear) ()

if (argc == 1)

bloquear = sin_bloqueo;

else if (argc == 2 && EQ (argv [1], "-b"))

bloquear = con_bloqueo;

else

{

fprintf (stderr, "Forma de uso: %s[-b]|n", argv [0];

exit (-1);

}

 

/* apertura archivo, valida existencia */

if ((fd = open(ARCHIVO, O_RDWRIO_CREAT, 0644)) == -1)

{

perror (ARCHIVO);

exit (-1);

}

 

/* cuando se crea el archivo, se inicializa el contador */

if (read (fd, &numero, sizeof(numero)) != sizeof(numero))

{

numero=0;

write (fd, &numero, sizeof(numero));

}

 

for (i=0; i<10; i++)

{

lseek (fd, OL, SEEK_SET);

/* bloqueo con candado de escritura */

(*bloquear) (fd, FWRLCK);

read (fd, &numero, sizeof(numero));

++numero;

sleep (1);

lseek (fd, OL, SEEK_SET);

write (fd, &numero, sizeof(numero));

printf ("PID = %d, nro = %d|n", getpid(), numero);

 

/* desbloquear, quitar el candado */

(*bloquear) (fd, F_UNLCK);

}

close (fd);

}

 

Nótese que la actualización del archivo se realiza en forma indivisible, de tal forma que cuando un proceso lee el número, lo encuentra actualizado por el último proceso que trabajó con el.

 

    1. Función lockf.

 

La llamada lockf bloquea la totalidad o parte de un archivo, permitiendo implementar bloqueos de tipo consultivo y obligatorio. En el bloqueo consultivo el sistema operativo conoce en cada momento que recursos se encuentran bloqueados y por que proceso, pero no prohibe a nadie que haga uso de sus recursos; la decisión de uso y las posteriores consecuencias dependen del usuario. En el bloqueo obligatorio el sistema operativo comprueba todos los accesos al recurso compartido con el objeto de negar accesos inconvenientes.

 

La opción F_LOCK bloquea el área respectiva para uso exclusivo del proceso, y en caso de no poderlo hacer duerme el proceso. La opción F_TLOCK chequea la disponibilidad del área antes de bloquearla en forma exclusiva, con la característica de que se hace en forma atómica y de que la no factibilidad de bloqueo producirá un error –1; de esta forma, el proceso no se dormirá. La opción F_ULOCK permite el desbloqueo.

 

La siguiente porción de un programa C ejemplifica los dos tipos de bloqueo, considerando que se usaran las opciones (-a) y (-m) en su ejecución:

 

If (EQ (argv [1], "-a"))

{

If (lockf (fd, F_TLOCK, OL) == -1)

{

fprintf (stderr, "Archivo bloqueado ",

argv[2]);

exit (-1);

}

}

else if (EQ (argv[1],"m"))

if (lockf (fd, F_LOCK, OL) == -1)

{

perror (argv[2]);

exit (-1);

}

 

Obsérvese que dependiendo de la opción que se utilice, el programa que se ejecute una vez el archivo esté bloqueado, se dormirá o no.

 

3. CONTROL DE CONCURRENCIA EN BASES DE DATOS.

 

El problema de integridad en una Base de Datos es más complicado de lo que parece, puesto que la mala sincronización de transacciones concurrentes puede ocasionar datos inesperados; por ejemplo, en una DB de reservas aéreas podría producirse que una misma silla de un avión en un vuelo específico sea vendida dos (2) veces, o en una DB bancaria podría ocurrir que se paguen dos cheques en forma simultánea sobre una cuenta que no tiene saldo suficiente para cubrirlos en su totalidad.

 

Por ejemplo, considérese una DB bancaria en la cual existe un cliente con un saldo de $200.000, y dos (2) transacciones que pretenden pagar en forma simultánea dos (2) cheques por valor de $150.000 y $120.000 respectivamente. Podría ocurrir lo siguiente:

 

T1 READ SALDO (200.000)

T2 READ SALDO (200.000)

T1 SALDO=SALDO-150.000 (=50.000)

T2 SALDO=SALDO-120.000 (=80.000)

T1 WRITE SALDO (50.000)

T2 WRITE SALDO (80.000)

 

De esta forma, el saldo final de la cuenta sería de $80.000, cuando en realidad el cliente estaría en sobregiro de $70.000.

 

Aún recurriendo a los mecanismos de bloqueo de información, la complejidad del sistema podría ocasionar datos inconsistentes si los candados no se sincronizan apropiadamente. La situación (a) presenta un caso de abrazo mortal, en el cual la transacción T1 está esperando que el dato B sea desbloqueado por T2, mientras la transacción T2 está esperando que el dato A sea desbloqueado por T1

 

(a) T1: LOCK A LOCK B UNLOCK A UNLOCK B

T2: LOCK B LOCK A UNLOCK B UNLOCK A

 

Encambio, la situación (b) muestra un escenario en el que los valores finales de A, B y C dependen del orden en que tres (3) transacciones simultáneas T1, T2 y T3 sean finalmente ejecutadas (las funciones F(X) corresponden a las operaciones efectuadas al dato X en el tiempo ocurrido desde su bloqueo hasta su desbloqueo)

 

(b)

(T1) (T2) (T3)

 

LOCK A LOCK B LOCK A

F1(A) F3(B) F6(A)

LOCK B LOCK C LOCK C

F2(B) F4(C) F7(C)

UNLOCK A UNLOCK B UNLOCK C

UNLOCK B LOCK A UNLOCK A

F5(A)

UNLOCK C

UNLOCK A

 

 

(1) T1:LOCK A A0 B0 C0

(2) T2:LOCK B A0 B0 C0

(3) T2:LOCK C A0 B0 C0

(4) T2:UNLOCK B A0 F3(B0) C0

(5) T1:LOCK B A0 F3(B0) C0

(6) T1:UNLOCK A F1(A0) F3(B0) C0

(7) T2:LOCK A F1(A0) F3(B0) C0

(8) T2:UNLOCK C F1(A0) F3(B0) F4(C0)

(9) T2:UNLOCK A F5(F1(A0)) F3(B0) F4(C0)

(10) T3:LOCK A F5(F1(A0)) F3(B0) F4(C0)

(11) T3:LOCK C F5(F1(A0)) F3(B0) F4(C0)

(12) T1:UNLOCK B F5(F1(A0)) F2(F3(B0)) F4(C0)

(13) T3: UNLOCK C F5(F1(A0)) F2(F3(B0)) F7(F4(C0))

(14) T3:UNLOCK A F6(F5(F1(A0))) F2(F3(B0)) F7(F4(C0))

 

El problema presentado es que dependiendo del orden de ejecución de T1, T2 y T3, los valores finales de A, B y C cambian; por ejemplo, si el orden fuera T1T2, el valor de B podría ser F3(F2(B0)), lo cual es diferente al valor obtenido. De otra forma, dependiendo del orden T2T1T3, T2T3T1 ó T3T2T1, el valor final de A podría ser F6(F1(F5(A0))), F1(F6(F5(A0))) ó F1(F5(F6(A0))).

 

 

 

La ejecución concurrente de varias transacciones es correcta solamente si su efecto es el mismo al obtenido cuando estas se corren en orden serial; es claro que en el ejemplo, esto no se da.

 

Con el fin de garantizar la seriabilidad de las transacciones se recomienda:

 

 

La situación ( c) muestra una serialización de las transacciones T1’, T2’ y T3’, puesto que independientemente del orden de ejecución , el resultado final de A, B y C es el mismo.

 

(c)

(T1’) (T2’) (T3’)

 

LOCK A LOCK A LOCK A

F1(A) F5(A) F6(A)

LOCK B LOCK B LOCK C

F2(B) F3(B) F7(C)

UNLOCK A LOCK C UNLOCK A

UNLOCK B F4 (C ) UNLOCK C

UNLOCK A

UNLOCK B

UNLOCK C

 

Se puede distinguir entre bloqueos de lectura o compartidos –RLOCK- para aquellos que solo impiden la acción de una transacción que desea escribir, y bloqueos de escritura –WLOCK- que impiden cualquier tipo de acceso por otra transacción. De esta forma, las ejecuciones de un conjunto de transacciones en diferentes órdenes (schedules) son equivalentes si:

 

 

Las transacciones que respeten los principios de seriabilidad y atomicidad garantizarán integridad en los datos. Nótese que la implementación de operaciones RLOCK, WLOCK y UNLOCK corresponden a programas del tipo presentado en la sección 1.

 

 

 

 

 

 

 

4. CONTROL DE CONCURRENCIA EN BD DISTRIBUIDAS.

 

El problema de concurrencia en las bases de datos distribuidas se presenta porque pueden existir múltiples copias de un item de datos almacenadas en diferentes nodos, y se debe garantizar que las copias son idénticas; igualmente debe asegurarse que la lectura de una de estas copias es consistente –su integridad-.

 

Se asume que las transacciones son de ejecución en dos fases y que existe seriabilidad y atomicidad local para las transacciones; sin embargo, los RLOCKS y WLOCKS deberán asegurar seriabilidad en el ambiente distribuido (una violación a la seriabilidad podría presentarse si existen dos transacciones teniendo un RLOCK una, y un WLOCK la otra, al mismo tiempo sobre el mismo item de datos).

 

Una de las estratégias de control consiste en asociar un bloqueo con cada copia del item de datos, y en otorgar o negar los bloqueos a cada transacción que requiera un RLOCK o WLOCK desde cada sitio de la copia. Debido a que el DBMS distribuido deberá ver los bloqueos sobre los items de datos en una forma global y no sobre las copias, se ha definido una regla para convertir los bloqueos sobre las copias en bloqueos sobre los items de datos, así:

 

 

Las reglas para otorgar o negar bloqueos son las mismas que para bases de datos centralizadas; se puede otorgar un RLOCK si ninguna otra transacción tiene un WLOCK en la copia, y se puede otorgar un WLOCK si ninguna otra transacción tiene un RLOCK o WLOCK. El efecto de esto es que dos (2) transacciones no pueden obtener un bloqueo de escritura y de lectura sobre el mismo item en el mismo momento. Este método garantiza que si el número de nodos n=1, entonces el sistema se comporta como si fuera el único sitio existente -DB centralizada-.

 

Si uno o más sitios niegan un requerimiento de bloqueo, entonces el bloque sobre el item es negado. Para evitar abrazos mortales, las transacciones deben informar los desbloqueos a cada uno de los sitios que otorgaron un bloqueo, en caso contrario dos transacciones podrían estar indefinidamente esperando por la liberación de un item que la otra tiene bloqueado.

 

5. BIBLIOGRAFÍA.

 

Hosted by www.Geocities.ws

1