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:
emptycontrola el número de espacios vacíos en el buffer,fullcontrola el número de ítems en el buffer, ymutexasegura 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_mutexcontrola el acceso exclusivo para los escritores, ymutexprotege la variableread_count. - Lector: Incrementa
read_county si es el primer lector, bloquea a los escritores (sem_wait(&rw_mutex)). Luego, decrementaread_county 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
