Programacion paralela en C : Semaforos

por:

Te enseñamos a utilizar semaforos para la programación paralela
Uno de los contenidos fundamentales de la programación paralela es la sincronización con semáforos. Este concepto es el que os vamos a explicar en este artículo.

¿Qué es un semáforo?

Por si quedaba alguna duda, no son semáforos de tráfico, es un concepto usado en informática para indicar una estructura con unas características específicas.

Un semáforo es una estructura u objeto que nos va a permitir la sincronización multiproceso o multihilo de nuestro programa. Básicamente se basa en un contador, una función de incrementar el contador y otra de decrementar el contador. El control de acceso se realiza mediante el valor de ese contador, si el contador es negativo el hilo o proceso se suspenderá hasta que ese contador deje de ser negativo, momento en el cual el hilo o proceso empezará a ejecutarse automáticamente.

La implementación de los semáforos varía entre plataformas, por ejemplo en Linux están implementadas a nivel del sistema operativo dentro de los IPCs.

Si estáis más interesados en cómo funciona un semáforo por dentro podéis leer los problemas: problema del barbero y el problema de los filósofos.

Funciones para gestionar un semáforo

plazasLibres

Un semáforo al final funciona de manera similar al letrero del parking que se puede ver en la imagen superior. Mientras que haya plazas libres, deja entrar a los procesos o hilos y cuando hay 0 plazas hay que esperar a que otros salgan para poder entrar.

La función wait comprueba que el valor del semáforo, si es negativo o cero el proceso se suspende sin consumir recursos hasta que el valor deje de ser negativo. Si el valor es positivo el proceso o hilo, sigue ejecutándose y se decrementa en una unidad el valor del semáforo. Siguiendo con la analogía del parking, hacer un wait es coger el ticket del aparcamiento cuando hay sitio.

La función signal incremental el valor del semáforo, este comportamiento debe realizarse cuando se ha salido de la zona de exclusión mutua. En nuestro caso sería similar a entregar el ticket y salir del parking.

Ejemplo de código usando semáforos en Linux

Este código muestra como funciona la exclusión mutua con semáforos:

/*
* Programa creado por Jorge Duran para Somos Binarios
*/

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>

/****************************************************************
Funciones auxiliares para inicializar, hacer wait y hacer Signal
Funcionan con arrays de semaforos, si solo hay uno ese parametro es 0
*******************************************************************/
void error(char* errorInfo) {
    fprintf(stderr,"%s",errorInfo);
    exit(1);
}

void doSignal(int semid, int numSem) {
    struct sembuf sops; //Signal
    sops.sem_num = numSem;
    sops.sem_op = 1;
    sops.sem_flg = 0;

    if (semop(semid, &sops, 1) == -1) {
        perror(NULL);
        error("Error al hacer Signal");
    }
}

void doWait(int semid, int numSem) {
    struct sembuf sops;
    sops.sem_num = numSem; /* Sobre el primero, ... */
    sops.sem_op = -1; /* ... un wait (resto 1) */
    sops.sem_flg = 0;

    if (semop(semid, &sops, 1) == -1) {
        perror(NULL);
        error("Error al hacer el Wait");
    }
}

void initSem(int semid, int numSem, int valor) { //iniciar un semaforo
  
    if (semctl(semid, numSem, SETVAL, valor) < 0) {        
    perror(NULL);
        error("Error iniciando semaforo");
    }
}

int main() {
    puts("Sincronizacion con Semaforos ");
    int semaforo;
    

    //Manera de usar semget http://pubs.opengroup.org/onlinepubs/7908799/xsh/semget.html
    //Creamos un semaforo y damos permisos para compartirlo
    if((semaforo=semget(IPC_PRIVATE,1,IPC_CREAT | 0700))<0) {
        perror(NULL);
        error("Semaforo: semget");
        }

    initSem(semaforo,0,1);
    puts("Hay una plaza libre");  

    switch (fork())
    {
        case -1:
            error("Error en el fork"); 

        case 0:  /* Hijo */
            doWait(semaforo,0);
            puts("Entro el hijo, el padre espera");
            sleep(5);
            puts("El hijo sale");
            doSignal(semaforo,0);
            exit(0);

        default: /* Padre */
            doWait(semaforo,0);
            puts("Entro el padre, el hijo espera");
            sleep(5);
            puts("El padre sale");
            doSignal(semaforo,0);
    }       
    
    sleep(20);
    
    //Liberacion del semaforo
    if ((semctl(semaforo, 0, IPC_RMID)) == -1) {
        perror(NULL);
        error("Semaforo borrando");
    }
    return 0;
}

La ejecución es similar a esta (según si entra antes el padre o el hijo, porque eso nunca lo podremos saber hasta ejecutarlo):

Uso de semaforos para controlar la exclusión mutua

Espero que os gusten esta serie de artículos y os animéis a usar estos conocimientos en algunos de vuestros proyectos.

Deja una Respuesta