En este tema, exploraremos algunos de los problemas clásicos de concurrencia que surgen en sistemas operativos. Estos problemas son fundamentales para entender cómo los sistemas operativos manejan múltiples procesos y hilos que compiten por recursos compartidos. Abordaremos los siguientes problemas:
- El Problema del Productor-Consumidor
- El Problema de los Filósofos Comensales
- El Problema de los Lectores y Escritores
- El Problema del Productor-Consumidor
Descripción
El problema del productor-consumidor, también conocido como el problema del buffer limitado, involucra dos tipos de procesos: productores y consumidores. Los productores generan datos y los colocan en un buffer, mientras que los consumidores toman datos del buffer para procesarlos. El desafío es asegurarse de que los productores no añadan datos a un buffer lleno y que los consumidores no intenten tomar datos de un buffer vacío.
Solución con Semáforos
Utilizaremos semáforos para sincronizar el acceso al buffer.
#include <stdio.h> #include <pthread.h> #include <semaphore.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; int count = 0; sem_t empty, full, mutex; void* producer(void* arg) { int item; while (1) { item = rand(); // Produce un ítem sem_wait(&empty); // Decrementa el semáforo empty sem_wait(&mutex); // Entra en la sección crítica buffer[count++] = item; // Añade el ítem al buffer sem_post(&mutex); // Sale de la sección crítica sem_post(&full); // Incrementa el semáforo full } } void* consumer(void* arg) { int item; while (1) { sem_wait(&full); // Decrementa el semáforo full sem_wait(&mutex); // Entra en la sección crítica item = buffer[--count]; // Toma un ítem del buffer sem_post(&mutex); // Sale de la sección crítica sem_post(&empty); // Incrementa el semáforo empty // Consume el ítem } } int main() { pthread_t prod, cons; sem_init(&empty, 0, BUFFER_SIZE); sem_init(&full, 0, 0); sem_init(&mutex, 0, 1); pthread_create(&prod, NULL, producer, NULL); pthread_create(&cons, NULL, consumer, NULL); pthread_join(prod, NULL); pthread_join(cons, NULL); return 0; }
Explicación del Código
- Semáforos:
empty
controla el número de espacios vacíos en el buffer,full
controla el número de ítems en el buffer, ymutex
asegura la exclusión mutua. - Productor: Produce un ítem, espera si el buffer está lleno (
sem_wait(&empty)
), entra en la sección crítica (sem_wait(&mutex)
), añade el ítem al buffer, y luego sale de la sección crítica y señala que hay un ítem más en el buffer (sem_post(&full)
). - Consumidor: Espera si el buffer está vacío (
sem_wait(&full)
), entra en la sección crítica (sem_wait(&mutex)
), toma un ítem del buffer, y luego sale de la sección crítica y señala que hay un espacio vacío más en el buffer (sem_post(&empty)
).
- El Problema de los Filósofos Comensales
Descripción
El problema de los filósofos comensales involucra cinco filósofos que alternan entre pensar y comer. Cada filósofo necesita dos tenedores para comer, pero hay solo cinco tenedores disponibles. El desafío es evitar la condición de carrera y el bloqueo mutuo (deadlock).
Solución con Semáforos
Utilizaremos semáforos para controlar el acceso a los tenedores.
#include <stdio.h> #include <pthread.h> #include <semaphore.h> #define N 5 sem_t forks[N]; void* philosopher(void* num) { int id = *(int*)num; while (1) { printf("Filósofo %d está pensando.\n", id); sem_wait(&forks[id]); // Toma el tenedor izquierdo sem_wait(&forks[(id + 1) % N]); // Toma el tenedor derecho printf("Filósofo %d está comiendo.\n", id); sem_post(&forks[id]); // Suelta el tenedor izquierdo sem_post(&forks[(id + 1) % N]); // Suelta el tenedor derecho } } int main() { pthread_t philosophers[N]; int ids[N]; for (int i = 0; i < N; i++) { sem_init(&forks[i], 0, 1); ids[i] = i; } for (int i = 0; i < N; i++) { pthread_create(&philosophers[i], NULL, philosopher, &ids[i]); } for (int i = 0; i < N; i++) { pthread_join(philosophers[i], NULL); } return 0; }
Explicación del Código
- Semáforos: Cada tenedor es representado por un semáforo.
- Filósofo: Cada filósofo toma primero el tenedor izquierdo (
sem_wait(&forks[id])
) y luego el derecho (sem_wait(&forks[(id + 1) % N])
), come, y luego suelta los tenedores (sem_post(&forks[id])
ysem_post(&forks[(id + 1) % N])
).
- El Problema de los Lectores y Escritores
Descripción
El problema de los lectores y escritores involucra múltiples procesos que leen y escriben en una base de datos compartida. Los lectores pueden leer simultáneamente, pero los escritores necesitan acceso exclusivo. El desafío es evitar la condición de carrera y asegurar que los escritores no sean bloqueados indefinidamente.
Solución con Semáforos
Utilizaremos semáforos para sincronizar el acceso a la base de datos.
#include <stdio.h> #include <pthread.h> #include <semaphore.h> sem_t rw_mutex, mutex; int read_count = 0; void* reader(void* arg) { while (1) { sem_wait(&mutex); read_count++; if (read_count == 1) { sem_wait(&rw_mutex); } sem_post(&mutex); // Leer la base de datos sem_wait(&mutex); read_count--; if (read_count == 0) { sem_post(&rw_mutex); } sem_post(&mutex); } } void* writer(void* arg) { while (1) { sem_wait(&rw_mutex); // Escribir en la base de datos sem_post(&rw_mutex); } } int main() { pthread_t r1, r2, w1; sem_init(&rw_mutex, 0, 1); sem_init(&mutex, 0, 1); pthread_create(&r1, NULL, reader, NULL); pthread_create(&r2, NULL, reader, NULL); pthread_create(&w1, NULL, writer, NULL); pthread_join(r1, NULL); pthread_join(r2, NULL); pthread_join(w1, NULL); return 0; }
Explicación del Código
- Semáforos:
rw_mutex
controla el acceso exclusivo para los escritores, ymutex
protege la variableread_count
. - Lector: Incrementa
read_count
y si es el primer lector, bloquea a los escritores (sem_wait(&rw_mutex)
). Luego, decrementaread_count
y si es el último lector, permite a los escritores (sem_post(&rw_mutex)
). - Escritor: Bloquea el acceso exclusivo (
sem_wait(&rw_mutex)
) y luego escribe en la base de datos, liberando el acceso después (sem_post(&rw_mutex)
).
Conclusión
En esta sección, hemos cubierto tres problemas clásicos de concurrencia: el problema del productor-consumidor, el problema de los filósofos comensales y el problema de los lectores y escritores. Cada uno de estos problemas ilustra desafíos comunes en la gestión de procesos concurrentes y cómo los semáforos pueden ser utilizados para sincronizar el acceso a recursos compartidos. Estos conceptos son fundamentales para entender cómo los sistemas operativos manejan la concurrencia y aseguran la integridad de los datos.
A continuación, pasaremos a explorar las estructuras de archivos en el Módulo 4.
Fundamentos de Sistemas Operativos
Módulo 1: Introducción a los Sistemas Operativos
- Conceptos Básicos de Sistemas Operativos
- Historia y Evolución de los Sistemas Operativos
- Tipos de Sistemas Operativos
- Funciones Principales de un Sistema Operativo
Módulo 2: Gestión de Recursos
Módulo 3: Concurrencia
- Conceptos de Concurrencia
- Hilos y Procesos
- Sincronización y Exclusión Mutua
- Problemas Clásicos de Concurrencia