0% encontró este documento útil (0 votos)
47 vistas20 páginas

Problemas Clásicos de Concurrencia en Sistemas

Este documento explica conceptos fundamentales sobre concurrencia en sistemas operativos, incluyendo problemas clásicos como el de lectores-escritores y productor-consumidor. También describe cómo los procesos concurrentes pueden interactuar y competir por recursos, creando condiciones de carrera. Finalmente, presenta diferentes mecanismos como semáforos que los sistemas operativos usan para coordinar el acceso a recursos compartidos y evitar interferencias entre procesos.

Cargado por

lautaritoliberti
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Temas abordados

  • programación de hilos,
  • interbloqueo,
  • código máquina,
  • variables condicionales,
  • programación de sistemas,
  • mecanismos de sincronización,
  • optimización de procesos,
  • programación concurrente,
  • semáforos,
  • bloqueo de procesos
0% encontró este documento útil (0 votos)
47 vistas20 páginas

Problemas Clásicos de Concurrencia en Sistemas

Este documento explica conceptos fundamentales sobre concurrencia en sistemas operativos, incluyendo problemas clásicos como el de lectores-escritores y productor-consumidor. También describe cómo los procesos concurrentes pueden interactuar y competir por recursos, creando condiciones de carrera. Finalmente, presenta diferentes mecanismos como semáforos que los sistemas operativos usan para coordinar el acceso a recursos compartidos y evitar interferencias entre procesos.

Cargado por

lautaritoliberti
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Temas abordados

  • programación de hilos,
  • interbloqueo,
  • código máquina,
  • variables condicionales,
  • programación de sistemas,
  • mecanismos de sincronización,
  • optimización de procesos,
  • programación concurrente,
  • semáforos,
  • bloqueo de procesos

Concurrencia

Concurrencia

Referencias

Descarga en PDF
LECCIÓN 1 de 3

Concurrencia

Concurrencia

Como usuarios de una computadora, queremos que esta ejecute la mayor


cantidad de procesos en la menor cantidad de tiempo posible. Pero, si
recordamos algunos conceptos de arquitectura del computador, sabremos
que un procesador es capaz de ejecutar un proceso a la vez. La utilización de
un ambiente de multiprocesador o de sistemas distribuidos mejora el
rendimiento porque varios procesos pueden ejecutarse simultáneamente.

Los procesos e hilos pueden requerir transferir datos entre ellos, depender
uno de otro y, además, competir por el uso del procesador.

Gestionar estas tres características y evitar interferencias entre procesos o


hilos son funciones que le corresponden al sistema operativo.

Problemas clásicos de concurrencia

Problema de los lectores-escritores


En este problema …existe un determinado objeto, …que puede
ser un archivo, un registro dentro de un archivo, etc., que va a ser
utilizado y compartido por una serie de procesos concurrentes.
Algunos de estos procesos sólo van a acceder al objeto sin
modificarlo, mientras que otros van a acceder al objeto para
modificar su contenido. Esta actualización implica leerlo,
modificar su contenido y escribirlo. A los primeros procesos se
los denomina lectores y a los segundos se los denomina
escritores. En este tipo de problemas existe una serie de
restricciones que han de seguirse:

Sólo se permite que un escritor tenga acceso al objeto al mismo


tiempo. Mientras el escritor esté accediendo al objeto, ningún otro
proceso lector ni escritor podrá acceder a él.

Se permite, sin embargo, que múltiples lectores tengan acceso al


objeto, ya que nunca van a modificar el contenido del mismo.

En este tipo de problemas es necesario disponer de servicios de


sincronización que permitan a los procesos lectores y escritores
sincronizarse adecuadamente en el acceso al objeto. (Silva,
2015, pp. 158 y 159).
Figura 1: Recurso

Fuente: Silva, 2015, p. 159.

Problema del productor-consumidor

El problema del productor-consumidor es uno de los problemas


más habituales que surge cuando se programan aplicaciones
utilizando procesos concurrentes. En este tipo de problemas,
uno o más procesos, que se denominan productores, generan
cierto tipo de datos que son utilizados o consumidos por otros
procesos, que se denominan consumidores. Un claro ejemplo
de este tipo de problemas es el de un compilador. El compilador
hace las funciones de productor al generar el código
ensamblador que consumirá proceso ensamblador para generar
el código máquina.
En esta clase de problemas, es necesario disponer de algún
mecanismo de comunicación que permita a los procesos
productor y consumidor intercambiar información. Ambos
procesos, además, deben sincronizar su acceso al mecanismo
de comunicación para que la interacción entre ellos no sea
problemática: cuando el mecanismo de comunicación se llene,
el proceso productor deberá quedar bloqueado hasta que haya
hueco para seguir insertando elementos. A su vez, el proceso
consumidor deberá quedarse bloqueado cuando el mecanismo
de comunicación este vacío, ya que en este caso no podrá
continuar su ejecución al no disponer de información a consumir.
Por tanto, este tipo de problema requiere servicios para que los
procesos puedan comunicarse y servicios para que se
sincronicen a la hora de acceder al mecanismo de
comunicación. (Silva, 2015, p. 158).

Figura 2: Mecanismo de la comunicación

Fuente: Sistemas Operativos Modernos, 2016, [Link]


Ejecución concurrente de procesos

Los procesos que se ejecutan de forma concurrente en un


sistema se pueden clasificar como procesos independientes o
cooperantes. …Tanto si los procesos son independientes como
cooperantes, pueden producirse una serie de interacciones
entre ellos. Estas interacciones pueden ser de dos tipos:

Interacciones motivadas porque los procesos comparten o


compiten por el acceso a recursos físicos o lógicos. Esta
situación aparece en los distintos tipos de procesos
anteriormente comentados. Por ejemplo, dos procesos
totalmente independientes pueden competir por el acceso a
disco. En este caso, el sistema operativo deberá encargarse de
que los dos procesos accedan ordenadamente sin que se cree
ningún conflicto. Esta situación también aparece cuando varios
procesos desean modificar el contenido de un registro de una
base de datos. Aquí es el gestor de la base de datos el que se
tendrá que encargar de ordenar los distintos accesos al registro.

Interacción motivada porque los procesos se comunican y


sincronizan entre sí para alcanzar un objetivo común. Por
ejemplo, un compilador se puede construir mediante dos
procesos: el compilador propiamente dicho, que se encarga de
generar código ensamblador, y el proceso ensamblador, que
obtiene código en lenguaje máquina a partir del ensamblador.
Estos dos tipos de interacciones obligan al sistema operativo a
incluir mecanismos y servicios que permitan la comunicación y
la sincronización entre procesos. (Sistemas Operativos
Modernos, 2016, [Link]

Condiciones de carrera

El primer problema con el que un sistema operativo debe lidiar son las
denominadas condiciones de carrera. Este inconveniente surge porque
existen componentes compartidos por varios procesos, como, por ejemplo,
archivos o sectores de memoria. Podría darse que dos procesos interfieran
entre sí al querer utilizar un mismo sector de memoria: el que llega primero
ocupa este sector porque no es consciente de que el otro también quiere
usarlo. Pero si el otro proceso tampoco sabe que el primero está a punto de
usar ese sector, puede, también, considerarse vacío y usarlo.

La solución parece simple: prohibir que estos dos procesos hagan uso de
algo compartido, lo que se denomina exclusión mutua. Pero en realidad no lo
es tanto, ya que también es necesario que los procesos cooperen entre sí y
compartan información.

Se denomina región o sección crítica a la parte de un programa que tiene que


hacer uso de algo compartido, por ejemplo, la memoria. En la Figura 3 se
observa cómo funciona este esquema. Cuando el proceso A ingresa en su
región crítica y, un momento después, el proceso B intenta también hacerlo,
este último se bloquea. Esto asegura que solo un proceso esté en su región
crítica.

Existen diferentes opciones para lograr este objetivo.

Figura 3: Exclusión mutua con regiones críticas

Fuente: Tanenbaum, 2009, p. 120.

Deshabilitación de interrupciones

Cuando un proceso ingresa en su región crítica, puede deshabilitar por


hardware las interrupciones. De esta forma, ningún otro proceso podrá
interrumpir al procesador. Son muchas las desventajas de este método: si,
por algún motivo, un proceso no vuelve a habilitar las interrupciones, el
sistema no puede continuar operando. Además, en un ambiente con más de
un procesador, un proceso solo puede bloquear a uno, entonces, otro proceso
podría ejecutarse en otro procesador y acceder al mismo sector de memoria.

Variables de candado

Este método se basa en software, en lugar de en hardware. Podemos utilizar


la variable denominada candado, para que sea consultada por cada proceso
que requiere entrar a la región crítica. En ese caso, establecemos que, si no
hay otro proceso en la región crítica, el candado estará en 0, y que, si lo hay,
estará en 1. De esta forma, cada proceso puede consultar al candado antes
de ingresar en la región crítica.

Puede ocurrir una condición de carrera si un proceso se ejecuta y coloca el


candado en 1, justo luego de que otro proceso consultara el candado, lo
encontrara en 0 y entrara a su región crítica.

Alternancia estricta

Este método consiste en examinar de forma permanente una variable similar


a la explicada en la variable de candado. La ventaja es que, de esta forma, un
proceso se asegura no ingresar a su región crítica si otro está en la propia. La
desventaja es que la examinación constante consume tiempo de CPU, por lo
que debe utilizarse cuando se puede determinar que la espera no será larga.

Semáforos
Un semáforo funciona de la siguiente manera: la variable denominada
Semáforo toma el valor 0 si no guardó ninguna señal de despertar, y valores
positivos cuando hay una o más señales de despertar pendientes.

Las señales de despertar permiten que se active el proceso que actualmente


está dormido (es decir, bloqueado) porque no puede entrar en su región
crítica.

El funcionamiento del semáforo involucra las operaciones Down (bajar) y Up


(subir). La operación Down supone que la variable Semáforo es mayor a 1, y
lo que hace es decrementar en 1. En cambio, si el proceso encuentra la
variable en 0, se llama a Sleep (pasa a estado Dormido). Solo un proceso
puede usar este proceso, y los demás deben esperar su turno, para evitar
posibles condiciones de carrera.

La operación Up permite que, si el semáforo tenía más de un proceso


inactivo, se seleccione uno de estos y se complete la operación Down a partir
de despertarlo.

Para conocer cómo es posible solucionar el problema del


productor/consumidor mediante la utilización de semáforos, consulta la
sección 2.3 del libro de Tanenbaum.

Mutex
Los mutex son versiones simplificadas de los semáforos. Empleados con
eficiencia y facilidad, resultan muy útiles para paquetes de hilos que se
implementan, en su totalidad, en el plano del usuario (Tanenbaum, 2005).

Supongamos que tenemos dos hilos (h1 y h2) que leen la variable “valor” que
inicialmente tiene un valor de 5. Si ambas desean aumentar el valor de esta
variable puede ocurrir lo siguiente:

el hilo h1 lee valor (5);

el hilo h2 lee valor (5);

el hilo h2 aumenta en 3 valor (5+3=8) y guarda;

el hilo h1 aumenta en 2 valor (5+2=7) y guarda.

Una vez finalizado, este “valor” es igual a 7 cuando debería ser igual a 10.
Para evitar esto empleamos como mecanismo de sincronización los mutex.
Los mutex nos ayudarán a bloquear los accesos a datos; mientras un
proceso ligero (hilo) esté accediendo a una sección crítica, otro proceso no
podrá acceder a ella y esperará a que liberen el mutex para acceder. Las
variables mutex son del tipo pthread_mutex_t. Los métodos para emplear los
mutex son los siguientes:
int pthread_mutex_init(pthread_mutex_t *mutex,
pthread_mutexattr_t * attr): Inicializa el mutex;

int pthread_mutex_destroy(pthread_mutex_t *mutex): destruye el


mutex (lo elimina de la memoria);

int pthread_mutex_lock(pthread_mutex_t *mutex): bloquea el mutex


si no lo tiene nadie. Si alguien tiene bloqueado el mutex el proceso
espera hasta que el proceso que lo tiene bloqueado lo libere;

int pthread_mutex_unlock(pthread_mutex_t *mutex): libera el mutex.

En conclusión, un mutex es el mecanismo de sincronización de procesos


ligeros más sencillo y eficiente. Los mutex se emplean para obtener acceso
exclusivo a recursos compartidos y para asegurar la exclusión mutua sobre
secciones críticas. Sobre un mutex se pueden realizar dos operaciones
atómicas básicas.

Lock: intenta bloquear el mutex. Si el mutex ya está bloqueado por


otro proceso, el proceso que realiza la operación se bloquea. En
caso contrario, se bloquea el mutex sin bloquear el proceso.

Unlock: desbloquea el mutex. Si existen procesos bloqueados en él,


se desbloqueará a uno de ellos, que será el nuevo proceso que
adquiera el mutex. La operación unlock sobre un mutex debe
ejecutarla el proceso ligero que adquirió con anterioridad el mutex
mediante la operación lock.

Dado que las operaciones lock y unlock son atómicas, solo un proceso
conseguirá bloquear el mutex y podrá continuar su ejecución dentro de la
sección crítica. El segundo proceso se bloqueará hasta que el primero libere
el mutex mediante la operación unlock.

Figura 4: Mutex en Pthreads

Fuente: Mutex en Pthreads (POSIX Threads)

En la Figura 4 están presentes las llamadas para bloquear y desbloquear un


mutex, y otras disponibles cuando se utilizan mutexes en Pthreads.
Tabla 1: Llamadas al sistema para mutex

Fuente: Tanenbaum, 2009, p. 133.

PThreads, además, utiliza variables de condición. Lo que estas variables


hacen es permitir que el productor se bloquee cuando no hay más espacio
disponible en el buffer, algo que no es posible realizar solo con el mutex.

Así como existen variables para la utilización de mutex, existen otras


relacionadas con las variables de condición. Por ejemplo, Pthread_cond_init,
que crea una variable de condición.

Variables condicionales

Una variable condicional es una variable de sincronización asociada a un


mutex que se utiliza para bloquear a un proceso hasta que ocurra algún
suceso. Las variables condicionales tienen dos operaciones atómicas:
“esperar” y “señalizar”.
c_wait: bloquea al proceso que ejecuta la llamada y lo expulsa del
mutex dentro del cual se ejecuta y al que está asociado la variable
condicional, permitiendo que algún otro proceso adquiera el mutex.
El bloqueo del proceso y la liberación del mutex se realizan de
forma atómica.

c_signal: desbloquea uno o varios procesos suspendidos en la


variable condicional. El proceso que se despierta compite de nuevo
por el mutex.

Figura 5: c_wait, c_signal

Fuente: elaboración propia.


Monitores

La operación con semáforos y mutexes puede derivar en un problema grave,


conocido como deadlock o interbloqueo. Si esto ocurre, tanto el productor
como el consumidor se bloquean y no se continúan realizando trabajos. Este
tipo de problemas se deben a errores en la programación de las aplicaciones.

Los monitores facilitan a los programadores la escritura de programas


correctos, es decir, evitan que se cometan errores que conduzcan a
deadlocks. Pero ¿qué es, en definitiva, un monitor?

Un monitor es una colección de procedimientos, variables y estructuras de


datos que se agrupan en un tipo especial de módulo o paquete. Los procesos
pueden llamar a los procedimientos en un monitor cada vez que lo desean,
pero no pueden acceder de manera directa a las estructuras de datos
internas del monitor desde procedimientos declarados fuera de este
(Tanenbaum, 2009).

En la Figura 6 se observa la estructura de un monitor. Solo un proceso puede


estar en el monitor, mientras que los restantes estarán bloqueados a la
espera de su turno. Si un proceso se bloquea dentro del monitor, pasará a
una cola de espera para poder volver a ingresar.

Figura 6: Estructura de un monitor


Fuente: Stallings, 2005, p. 230.

La ventaja que los monitores tienen sobre los semáforos es que todas las
funciones de sincronización están, en ellos, confinadas. Por lo tanto, es más
fácil comprobar que la sincronización se haya realizado correctamente y
detectar los errores. Es más, una vez que un monitor se ha programado
correctamente, el acceso al recurso protegido será correcto para todo
acceso desde cualquier proceso.
En cambio, en el caso de los semáforos, el acceso al recurso será correcto
solo si todos los procesos que acceden al recurso han sido programados
correctamente (Stallings, 2005).

Barreras

A diferencia de los métodos anteriores, este se basa en grupos de procesos,


y no en procesos individuales, ya que algunas aplicaciones se dividen en fase
y, para que un proceso pueda avanzar a la siguiente fase, todos los demás
deben estar listos para hacerlo. Entonces, los procesos se bloquean hasta
que el resto esté listo, como si existiera una barrera de contención.

C O NT I NU A R
LECCIÓN 2 de 3

Referencias

Stallings, W. (2005). Sistemas Operativos. España: Pearson Education.

Silva, M. (2015). Sistemas Operativos. Buenos Aires, Argentina: Alfaomega

Sistemas Operativos Modernos. (16 de junio de 2016). Problemas clásicos


de comunicación entre procesos. En Sistemas Operativos Modernos.
Recuperado den [Link]
[Link]

Tanenbaum, A. (2009). Sistemas Operativos Modernos. México: Pearson


Education.
LECCIÓN 3 de 3

Descarga en PDF

Módulo 2 - Lectura [Link]


289.9 KB

Common questions

Con tecnología de IA

Los mecanismos de sincronización mal implementados en aplicaciones concurrentes complejas pueden conducir a una variedad de problemas críticos, como deadlocks, donde procesos se bloquean mutuamente sin poder avanzar. Además, pueden provocar condiciones de carrera y resultados inciertos al acceso simultáneo a recursos compartidos. La correcta implementación es vital para asegurar la coordinación entre procesos y maximizar la eficiencia y estabilidad del sistema. Un mal diseño podría resultar en un sistema improductivo y fallos inesperados, afectando gravemente su rendimiento .

Tanto los semáforos como los mutexes son mecanismos de sincronización utilizados para controlar el acceso a secciones críticas. Los semáforos pueden tener valores mayores que 1, permitiendo múltiples procesos en la sección crítica si el código está diseñado para ello, mientras que los mutexes son binarios y solo permiten un proceso a la vez. Ambos utilizan operaciones atómicas (P/V o lock/unlock) para modificar su estado y permiten gestionar la entrada y salida de procesos de las secciones críticas. Sin embargo, los semáforos son más generales, mientras que los mutexes son más simples y se usan principalmente para exclusión mutua .

La exclusión mutua es fundamental en ambientes con múltiples procesos para evitar condiciones de carrera, donde dos o más procesos intentan modificar o acceder a un recurso compartido simultáneamente, lo que podría llevar a resultados indeterminados o inconsistencias de datos. Asegurar que solo un proceso accede a una región crítica del código a la vez previene interferencias y asegura la coherencia y correcta ejecución de los procesos .

Los sistemas operativos lidian con las condiciones de carrera utilizando tanto técnicas de hardware como de software. Deshabilitar interrupciones por hardware es una forma, aunque tiene desventajas como el riesgo de que el sistema no pueda continuar si las interrupciones no se reactivan. Alternativamente, las soluciones de software incluyen semáforos, mutexes y monitores para controlar el acceso a secciones críticas. Estos mecanismos de sincronización aseguran que solo un proceso acceda al recurso compartido a la vez, reduciendo el riesgo de condiciones de carrera .

Un compilador puede emplear el problema productor-consumidor dividiendo su ejecución en dos procesos: el primero genera código intermedio o ensamblador actuando como productor, y el segundo consume este código para generar el lenguaje máquina final, actuando como consumidor. Al utilizar mecanismos de comunicación y sincronización adecuados, el compilador puede optimizar la carga de trabajo distribuyendo tareas entre los dos procesos, permitiendo una ejecución síncrona y efectiva cuya eficacia depende del uso de buffers que previenen bloqueos hasta que haya espacio disponible .

Los monitores ofrecen ventajas sobre los semáforos debido a que encapsulan todas las funciones de sincronización dentro de un módulo, lo que facilita la verificación de que la sincronización se haya realizado correctamente y la detección de errores de programación. Una vez que un monitor está correctamente implementado, el acceso al recurso protegido es correcto para todos los procesos. En cambio, el uso de semáforos requiere que todos los procesos que acceden al recurso estén programados correctamente para asegurar el acceso correcto y evitar deadlocks .

Las 'barreras' en sincronización de procesos son un mecanismo que impide que un grupo de procesos avancen a la siguiente fase de ejecución hasta que todos estén listos, actuando como una barrera de contención. Este método es útil en aplicaciones donde las tareas se dividen en fases y es necesario que todos los procesos completen una fase antes de iniciar la siguiente. Esto asegura que todos los procesos estén sincronizados y preparados, evitando situaciones donde algunos procesos continúan avanzando sin esperar a otros .

El problema de los lectores-escritores se presenta cuando múltiples procesos necesitan acceder a un recurso compartido para leer o modificar su contenido. Las restricciones clave para evitar conflictos son: permitir que múltiples lectores accedan al recurso simultáneamente sin modificarlo y garantizar que solo un escritor acceda al recurso a la vez, bloqueando tanto a otros escritores como a lectores durante su acceso. Esto previene modificaciones concurrentes que podrían llevar a inconsistencias .

En PThreads, las variables de condición permiten que los procesos se bloqueen hasta que ocurra un evento, proporcionando una forma de que los productores o consumidores esperen por recursos sin desperdiciar ciclos de CPU. Cuando un buffer está lleno, los productores pueden esperar sin ocupar espacio en la sección crítica asociada. El uso de variables de condición complementa los mutex porque permite desbloquear procesos cuando el evento esperado ocurre mediante operaciones de 'señalizar', mientras que el mutex solo proporciona una exclusión mutua básica y no puede condicionar el acceso a eventos específicos .

Los sistemas operativos son responsables de gestionar la concurrencia al coordinar la transferencia de datos entre procesos, manejar las dependencias entre procesos y evitar las interferencias entre ellos. Un ambiente multiprocesador o distribuido mejora el rendimiento de la ejecución concurrente, pues permite que varios procesos se ejecuten simultáneamente. Sin embargo, la comunicación y sincronización adecuada entre procesos es esencial para evitar conflictos y condiciones de carrera .

También podría gustarte