in

Bloqueo de mutex para la sincronización de subprocesos de Linux

gfg 200x200 min

Requisito previo : Múltiples subprocesos en C

Sincronización de subprocesos se define como un mecanismo que asegura que dos o más procesos o subprocesos concurrentes no ejecuten simultáneamente algún segmento de programa en particular conocido como sección crítica. El acceso de los procesos a la sección crítica se controla mediante técnicas de sincronización. Cuando un subproceso comienza a ejecutar la sección crítica (un segmento serializado del programa), el otro subproceso debe esperar hasta que finalice el primer subproceso. Si no se aplican las técnicas de sincronización adecuadas, puede causar una condición de carrera donde los valores de las variables pueden ser impredecibles y varían según los tiempos de los cambios de contexto de los procesos o subprocesos.

Problemas de sincronización de subprocesos
Un código de ejemplo para estudiar problemas de sincronización:

#include <pthread.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

 

pthread_t tid[2];

int counter;

 

void* trythis(void* arg)

{

    unsigned long i = 0;

    counter += 1;

    printf("n Job %d has startedn", counter);

 

    for (i = 0; i < (0xFFFFFFFF); i++)

        ;

    printf("n Job %d has finishedn", counter);

 

    return NULL;

}

 

int main(void)

{

    int i = 0;

    int error;

 

    while (i < 2) {

        error = pthread_create(&(tid[i]), NULL, &trythis, NULL);

        if (error != 0)

            printf("nThread can't be created : [%s]", strerror(error));

        i++;

    }

 

    pthread_join(tid[0], NULL);

    pthread_join(tid[1], NULL);

 

    return 0;

}

¿Cómo compilar el programa anterior?
Para compilar un programa multiproceso usando gcc, necesitamos vincularlo con la biblioteca pthreads. A continuación se muestra el comando utilizado para compilar el programa.

[email protected]:~/$ gcc filename.c -lpthread

En este ejemplo, se crean dos subprocesos (trabajos) y en la función de inicio de estos subprocesos, se mantiene un contador para obtener los registros sobre el número de trabajo que se inició y cuando se completó.

Producción :

Job 1 has started
Job 2 has started
Job 2 has finished
Job 2 has finished

Problema: De los dos últimos registros, se puede ver que el registro ‘El trabajo 2 ha terminado‘se repite dos veces mientras no hay registro para’El trabajo 1 ha terminado‘ es visto.

¿Por qué ha ocurrido?
Al observar de cerca y visualizar la ejecución del código, podemos ver que:

  • El registro ‘El trabajo 2 ha comenzado‘se imprime justo después de’El trabajo 1 ha comenzado‘, por lo que se puede concluir fácilmente que mientras el hilo 1 estaba procesando, el planificador programó el hilo 2.
  • Si asumimos la suposición anterior como verdadera, entonces el valor de ‘encimeraLa variable ‘se incrementó de nuevo antes de que finalizara el trabajo 1.
  • Entonces, cuando el Trabajo 1 realmente se terminó, el valor incorrecto del contador produjo el registro ‘El trabajo 2 ha terminado‘ Seguido por el ‘El trabajo 2 ha terminado‘para el trabajo real 2 o viceversa, ya que depende del planificador.
  • Entonces vemos que no es el registro repetitivo sino el valor incorrecto de la variable ‘contador’ el problema.
  • El problema real fue el uso de la variable ‘contador’ por un segundo hilo cuando el primer hilo lo estaba usando o estaba a punto de usarlo.
  • En otras palabras, podemos decir que la falta de sincronización entre los subprocesos al usar el ‘contador’ de recursos compartidos causó los problemas o, en una palabra, podemos decir que este problema ocurrió debido a un ‘Problema de sincronización’ entre dos subprocesos.

    Cómo resolverlo ?

    La forma más popular de lograr la sincronización de subprocesos es mediante el uso de Mutexes.

    Mutex

    • Un Mutex es un bloqueo que establecemos antes de usar un recurso compartido y liberamos después de usarlo.
    • Cuando se establece el bloqueo, ningún otro hilo puede acceder a la región de código bloqueada.
    • Entonces vemos que incluso si el hilo 2 está programado mientras que el hilo 1 no tuvo acceso al recurso compartido y el código está bloqueado por el hilo 1 usando mutexes, el hilo 2 ni siquiera puede acceder a esa región de código.
    • Por tanto, esto garantiza el acceso sincronizado de los recursos compartidos en el código.

    Trabajo de un mutex

    1. Supongamos que un hilo ha bloqueado una región de código usando mutex y está ejecutando ese fragmento de código.
    2. Ahora, si el planificador decide hacer un cambio de contexto, entonces se desbloquean todos los demás subprocesos que están listos para ejecutar la misma región.
    3. Solo uno de todos los subprocesos llegaría a la ejecución, pero si este subproceso intenta ejecutar la misma región de código que ya está bloqueada, volverá a dormirse.
    4. El cambio de contexto se llevará a cabo una y otra vez, pero ningún subproceso podrá ejecutar la región bloqueada del código hasta que se libere el bloqueo mutex.
    5. El bloqueo de mutex solo lo liberará el hilo que lo bloqueó.
    6. Por lo tanto, esto asegura que una vez que un hilo ha bloqueado un fragmento de código, ningún otro hilo puede ejecutar la misma región hasta que sea desbloqueado por el hilo que lo bloqueó.

    Por lo tanto, este sistema garantiza la sincronización entre los subprocesos mientras se trabaja en recursos compartidos.

    Se inicializa un mutex y luego se logra un bloqueo llamando a las dos funciones siguientes: La primera función inicializa un mutex y, a través de la segunda función, se puede bloquear cualquier región crítica del código.

    1. int pthread_mutex_init (pthread_mutex_t * restringir mutex, const pthread_mutexattr_t * restringir atributo): Crea un mutex, referenciado por mutex, con atributos especificados por attr. Si attr es NULL, se utiliza el atributo mutex predeterminado (NONRECURSIVE).

      Valor devuelto
      Si tiene éxito, pthread_mutex_init () devuelve 0 y el estado del mutex se inicializa y desbloquea.
      Si no tiene éxito, pthread_mutex_init () devuelve -1.

    2. int pthread_mutex_lock (pthread_mutex_t * mutex): Bloquea un objeto mutex, que identifica un mutex. Si el mutex ya está bloqueado por otro subproceso, el subproceso espera a que el mutex esté disponible. El subproceso que ha bloqueado un mutex se convierte en su propietario actual y sigue siendo el propietario hasta que el mismo subproceso lo haya desbloqueado. Cuando el mutex tiene el atributo de recursivo, el uso del bloqueo puede ser diferente. Cuando este tipo de exclusión mutua está bloqueado varias veces por el mismo hilo, entonces se incrementa un recuento y no se publica ningún hilo en espera. El hilo propietario debe llamar a pthread_mutex_unlock () la misma cantidad de veces para disminuir la cuenta a cero.

      Valor devuelto
      Si tiene éxito, pthread_mutex_lock () devuelve 0.
      Si no tiene éxito, pthread_mutex_lock () devuelve -1.

El mutex se puede desbloquear y destruir llamando a las siguientes dos funciones:La primera función libera el bloqueo y la segunda función destruye el bloqueo para que no se pueda usar en ningún lugar en el futuro.

  1. int pthread_mutex_unlock (pthread_mutex_t * mutex): Libera un objeto mutex. Si uno o más subprocesos están esperando para bloquear el mutex, pthread_mutex_unlock () hace que uno de esos subprocesos regrese de pthread_mutex_lock () con el objeto mutex adquirido. Si no hay subprocesos esperando el mutex, el mutex se desbloquea sin un propietario actual. Cuando el mutex tiene el atributo de recursivo, el uso del bloqueo puede ser diferente. Cuando este tipo de exclusión mutua está bloqueado varias veces por el mismo hilo, el desbloqueo disminuirá el recuento y no se publicará ningún hilo en espera para continuar ejecutándose con el bloqueo. Si el recuento se reduce a cero, entonces el mutex se libera y si algún hilo está esperando, se publica.

    Valor devuelto
    Si tiene éxito, pthread_mutex_unlock () devuelve 0.
    Si no tiene éxito, pthread_mutex_unlock () devuelve -1

  2. int pthread_mutex_destroy (pthread_mutex_t * mutex): Elimina un objeto mutex, que identifica un mutex. Los mutex se utilizan para proteger los recursos compartidos. mutex se establece en un valor no válido, pero se puede reinicializar usando pthread_mutex_init ().

    Valor devuelto
    Si tiene éxito, pthread_mutex_destroy () devuelve 0.
    Si no tiene éxito, pthread_mutex_destroy () devuelve -1.

mutex
Un ejemplo para mostrar cómo se utilizan las exclusiones mutuas para la sincronización de subprocesos

#include <pthread.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

 

pthread_t tid[2];

int counter;

pthread_mutex_t lock;

 

void* trythis(void* arg)

{

    pthread_mutex_lock(&lock);

 

    unsigned long i = 0;

    counter += 1;

    printf("n Job %d has startedn", counter);

 

    for (i = 0; i < (0xFFFFFFFF); i++)

        ;

 

    printf("n Job %d has finishedn", counter);

 

    pthread_mutex_unlock(&lock);

 

    return NULL;

}

 

int main(void)

{

    int i = 0;

    int error;

 

    if (pthread_mutex_init(&lock, NULL) != 0) {

        printf("n mutex init has failedn");

        return 1;

    }

 

    while (i < 2) {

        error = pthread_create(&(tid[i]),

                               NULL,

                               &trythis, NULL);

        if (error != 0)

            printf("nThread can't be created :[%s]",

                   strerror(error));

        i++;

    }

 

    pthread_join(tid[0], NULL);

    pthread_join(tid[1], NULL);

    pthread_mutex_destroy(&lock);

 

    return 0;

}

En el código anterior:

  • Un mutex se inicializa al comienzo de la función principal.
  • El mismo mutex está bloqueado en la función ‘trythis ()’ mientras se usa el recurso compartido ‘contador’.
  • Al final de la función ‘trythis ()’ se desbloquea el mismo mutex.
  • Al final de la función principal, cuando ambos subprocesos están terminados, el mutex se destruye.

Producción :

Job 1 started
Job 1 finished
Job 2 started
Job 2 finished

Así que esta vez están presentes los registros de inicio y finalización de ambos trabajos. Entonces, la sincronización de subprocesos se llevó a cabo mediante el uso de Mutex.

Referencias:
Sincronización (informática)
Lock (informática)

Este artículo es una contribución de Kishlay Verma. Si te gusta GeeksforGeeks y te gustaría contribuir, también puedes escribir un artículo usando contrib.geeksforgeeks.org o envíe su artículo por correo electrónico a [email protected] Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.

Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema discutido anteriormente.

¡Atención lector! No dejes de aprender ahora. Obtenga todos los conceptos importantes de la Teoría de la CS para las entrevistas SDE con el Curso de Teoría de CS a un precio asequible para los estudiantes y prepárese para la industria.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.

apple touch icon@2

html – ¿Cómo centrar un iframe horizontalmente?

300px Clarinets

Clarinete vs Oboe – Diferencia y Comparación