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:

  1. El Problema del Productor-Consumidor
  2. El Problema de los Filósofos Comensales
  3. El Problema de los Lectores y Escritores

  1. 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, y mutex 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)).

  1. 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]) y sem_post(&forks[(id + 1) % N])).

  1. 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, y mutex protege la variable read_count.
  • Lector: Incrementa read_count y si es el primer lector, bloquea a los escritores (sem_wait(&rw_mutex)). Luego, decrementa read_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.

© Copyright 2024. Todos los derechos reservados