Un artículo para comprender los métodos de sincronización de subprocesos/comunicación entre subprocesos de Linux: bloqueos mutex, bloqueos de lectura y escritura, bloqueos de giro, semáforos, variables de condición, señales, etc.

Tabla de contenido

Método de comunicación entre subprocesos/sincronización de subprocesos

mecanismo de bloqueo

exclusión mutua

Bloqueo de lectura-escritura (rwlock)

Bloqueo de giro (giro)

Mecanismo de semáforo

mecanismo de variable de condición

Señal


Método de comunicación entre subprocesos/sincronización de subprocesos

PD: Muchos de los siguientes párrafos son citas directas, sin utilizar el formato de "cita" de Markdown, y las fuentes se han publicado.

Referencia/Cita:

Dado que los recursos de variables de proceso se comparten entre subprocesos, el propósito de la comunicación entre subprocesos es principalmente para la sincronización de subprocesos (es decir, las reglas de orden (semáforos) o reglas de exclusión mutua (bloqueos mutex) que restringen la ejecución de múltiples subprocesos), por lo que los subprocesos son no como procesos Un mecanismo de comunicación utilizado para el intercambio de datos en las comunicaciones.

La diferencia entre bloqueos mutex, variables de condición y semáforos:

  • Bloqueo de exclusión mutua: exclusión mutua. Si un hilo ocupa un determinado recurso, otros hilos no pueden acceder a él. Solo cuando este hilo se desbloquea, otros hilos pueden acceder a él.

  • Semáforo: Sincronización. Cuando un hilo completa una acción, se lo dice a otros hilos a través del semáforo, y los otros hilos luego realizan ciertas acciones. Y el semáforo tiene una función más poderosa. El semáforo se puede utilizar como contador de recursos. El valor del semáforo se inicializa al número actualmente disponible de un determinado recurso. Disminuye después de usar uno y aumenta después de devolver uno.

  • Variable de condición: Sincronización. Cuando un subproceso completa una acción, envía una señal a otros subprocesos a través de la variable de condición, y los otros subprocesos luego realizan ciertas acciones. Las variables de condición deben usarse con bloqueos mutex.

El mecanismo de bloqueo es adecuado para situaciones similares a las operaciones atómicas. Después del bloqueo, los recursos en una determinada sección crítica se procesan rápidamente y luego se desbloquean inmediatamente. No es adecuado para bloqueos a largo plazo (el peor caso es que los recursos en la sección crítica sección después del bloqueo están bloqueadas). Después de ejecutar la interrupción, la interrupción debe bloquearse y bloquearse, luego se produce un punto muerto y la señal se atasca); y la señal/semáforo (y la variable de condición) es adecuada para esperas a largo plazo la señal/condición que se producirá y la persona que llama duerme durante el período de espera/bloqueo.

Para reducir errores y complejidad, antes de diseñar el programa, se debe intentar considerar no utilizar bloqueos, señales, etc. en las interrupciones. El programa de interrupción debe diseñarse de manera concisa y elegante.

mecanismo de bloqueo

exclusión mutua

El bloqueo mutex/mutex se utiliza para evitar que varios subprocesos accedan a un recurso crítico al mismo tiempo/concurrentemente.

Un mutex es esencialmente un bloqueo. El mutex se bloquea antes de acceder a un recurso compartido y se libera una vez completado el acceso. Una vez bloqueado el mutex, otros subprocesos que vuelvan a bloquear el mutex se bloquearán hasta que el subproceso actual libere el bloqueo mutex. Para los bloqueos mutex, si el recurso ya está ocupado, el solicitante del recurso solo puede ingresar al estado de suspensión.

Si se bloquea más de un subproceso cuando se libera el mutex, todos los subprocesos bloqueados en el bloqueo se volverán ejecutables. El primer subproceso que se vuelva ejecutable puede bloquear el mutex y otros subprocesos verán Cuando el mutex aún está bloqueado, solo puede bloquear nuevamente y esperar a que vuelva a estar disponible. De esta manera, solo un hilo puede ejecutarse hacia adelante a la vez.

Es decir, cuando varios subprocesos quieren acceder a un recurso crítico, como una variable global, necesitan acceder a él mutuamente: cuando yo accedo a él, tú no puedes acceder a él.

Archivo de encabezado: #include <pthread.h>.

API de uso común:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); // Cantidad de inicialización 
/* Esta función inicializa un mutex. El primer parámetro es el puntero mutex y el segundo parámetro es el atributo que controla el mutex. Generalmente NULL. Cuando la función tiene éxito, devolverá 0, lo que indica que el mutex se inicializó correctamente. 
    Por supuesto, el mutex inicializado también se puede inicializar rápidamente llamando a una macro. El código es el siguiente: 
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER; 
*/ 
​int
pthread_mutex_lock(pthread_mutex_t *mutex); // Lock (block) 
int pthread_mutex_unlock(pthread_mutex_t *mutex ); // Desbloquear (sin bloqueo) 
/* La función de bloqueo y la función de desbloqueo son funciones de bloqueo y desbloqueo respectivamente. Solo necesita pasar el puntero mutex pthread_mutex_t inicializado. Devolverá 0 en caso de éxito. 
    Cuando un hilo obtiene el derecho de ejecución, ejecuta la función de bloqueo. Una vez que el bloqueo se obtiene con éxito, otros hilos se bloquearán cuando encuentren la función de bloqueo hasta que el hilo que adquiere el recurso ejecute la función de desbloqueo. La función de desbloqueo activa otros subprocesos que esperan el mutex.
    Una nota especial es que después de adquirir el bloqueo, el desbloqueo debe ejecutarse después de que se complete el procesamiento lógico; de lo contrario, se producirá un punto muerto. Como resultado, los subprocesos restantes se bloquearon y no se pudieron ejecutar. Cuando utilice un mutex, preste especial atención al uso de la función pthread_cancel para evitar un punto muerto. 
* 
/
int pthread_mutex_trylock(pthread_mutex_t *mutex); // Bloqueo Mutex (sin bloqueo) 
/* Esta función también es una función de bloqueo de subprocesos, pero esta función está en modo sin bloqueo y utiliza el valor de retorno para determinar si el bloqueo es exitoso. El uso es el mismo que el anterior. Las funciones de bloqueo y bloqueo son consistentes. */ 
​int
pthread_mutex_destroy(pthread_mutex_t *mutex); // Destruye el mutex 
/* Esta función se utiliza para destruir el mutex. Al pasar el puntero del mutex, se puede completar la destrucción del mutex y se devuelve 0 con éxito . . */

Rutina: Referencia pthread库-线程编程例程-来自百问网\01_文档配套源码\Pthread_Text10.c.

Propiedades de bloqueo mutex:

/* Similar a los atributos del hilo, primero declare la variable, luego inicialícela con pthread_mutexattr_init, 
    luego use pthread_mutexattr_getxxx/pthread_mutexattr_setxxx para obtener/establecer una determinada opción del atributo, 
    luego complete el atributo al llamar al bloqueo mutex para inicializar pthread_mutex_init, 
    y finalmente destruirlo* / 
int pthread_mutexattr_init(pthread_mutexattr_t* attr); 
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr); 
​int
pthread_mutexattr_getshared(const pthread_mutexattr_t* attr, int* pshared); int pthread_mutexattr_setshared(pthread_mutexattr _t* attr, int* pshared); 
parámetros 
    pshared que se puede pasar en: 
        PTHREAD_PROCESS_SHARED: el bloqueo mutex se puede compartir entre procesos. 
        PTHREAD_PROCESS_PRIVATE: solo puede ser compartido por subprocesos en el proceso al que pertenece el subproceso inicializador. 
int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type); 
int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type); Escriba los 
    parámetros que pueden pasarse en:
        PTHREAD_MUTEX_NOMAL: Bloqueo justo, bloquear un bloqueo normal que ha sido bloqueado nuevamente causará un punto muerto; desbloquear un bloqueo normal que ha sido bloqueado por otro hilo, o desbloquear un bloqueo normal que ha sido desbloqueado nuevamente, conducirá a resultados impredecibles. 
        PTHREAD_MUTEX_ERRORCHECK: Bloqueo de verificación de errores. Si un bloqueo de verificación de errores ya bloqueado se vuelve a bloquear, la operación de bloqueo devuelve EDEADLOCK. Para desbloquear un bloqueo de verificación de errores que ha sido bloqueado por otro subproceso, o para desbloquear nuevamente un bloqueo de verificación de errores ya desbloqueado, la operación de desbloqueo devuelve EPERM. 
        PTHREAD_MUTEX_RECURSIVE: bloqueo anidado, el uso de error devuelve EPERM 
        PTHREAD_MUTEX_DEFAULT: similar al nominal.
Bloqueo de lectura-escritura (rwlock)

 Los bloqueos de lectura y escritura son similares a los mutex, pero los bloqueos de lectura y escritura tienen un mayor paralelismo. Un mutex está bloqueado o desbloqueado, y sólo un subproceso puede bloquearlo a la vez. El bloqueo de lectura y escritura tiene tres estados: estado bloqueado en modo lectura, estado bloqueado en modo escritura y estado desbloqueado .

Solo un subproceso puede mantener un bloqueo de lectura y escritura en modo de escritura a la vez, pero varios subprocesos pueden mantener un bloqueo de lectura y escritura en modo de lectura al mismo tiempo. Cuando el bloqueo de lectura y escritura está en el estado de bloqueo de escritura, todos los subprocesos que intenten bloquear el bloqueo se bloquearán hasta que se desbloquee el bloqueo. Cuando el bloqueo de lectura y escritura está en el estado de bloqueo de lectura, todos los subprocesos que intenten bloquearlo en modo de lectura pueden obtener acceso, pero cualquier subproceso que quiera bloquear el bloqueo en modo de escritura se bloqueará hasta que todos los subprocesos lo liberen. leer bloqueo.

Archivo de encabezado: #include <pthread.h>.

API de uso común:

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *rwlockattr); // Inicializa el bloqueo de lectura y escritura 
int
pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // Bloqueo del modo de lectura bloqueo de lectura y escritura 
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // Bloqueo del modo de escritura leer bloqueo de escritura 
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // Desbloquear el bloqueo de lectura y escritura 
int
pthread_rwlock_destroy(pthread_rwlock_t *rwlock); // Destruir el bloqueo de lectura y escritura
Bloqueo de giro (giro)

Si el mutex está ocupado (bloqueado) y entra otro subproceso, el mutex provocará el cambio de subproceso (sin esperar, sino ceder una vez para ejecutar otros subprocesos). Hay muchos contenidos adecuados para bloquear.

Para los bloqueos de giro, si el bloqueo está ocupado (bloqueado), el subproceso entrante esperará hasta que adquirir el bloqueo sea equivalente a while(1);(esperar hasta morir, ejecutarse durante un período de tiempo completo). Cuando el contenido adecuado para el bloqueo es relativamente pequeño y el costo de cambiar de hilo es mucho mayor que el costo de esperar, se utiliza un bloqueo giratorio. (Si sabe algo sobre RTOS, comprenderá los conceptos de rendimiento, ejecución de un segmento de tiempo completo, etc. Puede comprenderlo jugando FreeRTOS y RTT en un microcontrolador).

Un bloqueo de giro es similar a un mutex, pero en lugar de bloquear el proceso al dormir, permanece en un estado de bloqueo de espera ocupada (giro) antes de adquirir el bloqueo. Los bloqueos de giro se pueden utilizar en las siguientes situaciones: el bloqueo se mantiene por un corto tiempo y el hilo no quiere gastar demasiado en reprogramar.

Si un subproceso ejecutable intenta adquirir un bloqueo de giro en disputa (ya retenido), el subproceso siempre estará ocupado esperando, girando, es decir, inactivo, esperando que el bloqueo vuelva a estar disponible (esperando ser desbloqueado). Un bloqueo de giro en conflicto hace que el subproceso que lo solicita gire mientras espera que el bloqueo vuelva a estar disponible, lo que desperdicia tiempo de CPU, por lo que los bloqueos de giro no deben mantenerse durante largos períodos de tiempo. De hecho, esta es la intención original del diseño de bloqueo giratorio: realizar un bloqueo liviano en poco tiempo. Los bloqueos de giro no se pueden usar en programas de interrupción. La preferencia está deshabilitada mientras se mantiene el bloqueo de giro (no se permite que el kernel tenga preferencia).

Archivo de encabezado: #include <pthread.h>.

API de uso común:

int pthread_spin_init(pthread_spinlock_t *lock, int pshared); 
/* Función de inicialización de bloqueo de giro, no contiene ninguna variable de atributo, pshared puede seleccionar parámetros: 
    PTHREAD_PROCESS_PRIVATE Use este bloqueo solo en este proceso 
    PTHREAD_PROCESS_SHARED Este bloqueo puede estar ubicado en un objeto de memoria compartida , en Compartir entre múltiples procesos. 
    Si desea utilizar bloqueos de giro para sincronizar múltiples procesos, configure pshared en PTHREAD_PROCESS_SHARED y luego asigne el objeto pthread_spinlock_t en la memoria compartida del proceso (lo mismo ocurre con pthread_mutex_t). 
*/ 
​int
pthread_spin_destroy(pthread_spinlock_t *lock); 
​//
Operación de bloqueo de giro 
int pthread_spin_lock(pthread_spinlock_t *lock); 
int pthread_spin_trylock(pthread_spinlock_t *lock); 
int pthread_spin_unlock(pthread_spinlock_t *lock);

Mecanismo de semáforo

El semáforo es una cantidad mayor o igual a 0. Cuando es 0, el hilo se bloquea. Utilice la función sem_pos (sin bloqueo) para aumentar el semáforo en 1. La función sem_wait (bloqueante) disminuye el semáforo en 1 cuando es mayor que 0 y lo bloquea cuando es igual a 0.

Se puede utilizar para notificaciones y conteo:

  • Los semáforos pueden controlar el orden de ejecución de múltiples subprocesos que originalmente se ejecutaron aleatoriamente y fuera de orden de una manera controlable y predecible. Por ejemplo, el subproceso A está esperando algo y el subproceso B puede enviar una señal al subproceso A después de que completa el asunto.

  • Se puede usar un objeto "semáforo" cuando se necesita un contador para limitar la cantidad de subprocesos que pueden usar un recurso compartido. El semáforo almacena el valor de recuento de los subprocesos que actualmente acceden a un recurso específico. El valor de recuento es el número de subprocesos que aún pueden utilizar el recurso. Si este recuento llega a cero, todos los intentos de acceso al recurso controlado por este semáforo se colocan en una cola y esperan hasta que se agote el tiempo de espera o hasta que el valor del recuento sea distinto de cero.

Archivo de encabezado: #include <semaphore.h>.

API de uso común:

int sem_init(sem_t *sem, int pshared, unsigned int value); //Inicializar un semáforo 
/* Esta función puede inicializar un semáforo y el primer parámetro se pasa en un puntero de tipo sem_t. 
    El segundo parámetro pasado en 0 representa el control de subprocesos (utilizado dentro de varios subprocesos); de lo contrario, es control de procesos (utilizado por múltiples procesos). 
    El tercer parámetro representa el valor inicial del semáforo, 0 representa bloqueo, 1 y los números superiores representan el valor inicial (sin bloqueo). 
    Una vez completada la inicialización del semáforo, se devolverá 0 si la ejecución es exitosa. */ 
int sem_destroy(sem_t * sem); // Destruye el semáforo 
​int
sem_wait(sem_t * sem); // Disminuye el semáforo en 1 (bloqueo), operación P 
int sem_post(sem_t * sem); // Aumenta el semáforo en 1 (sin bloqueo), operación V 
int sem_trywait(sem_t *sem); // Disminuye el semáforo en 1 (sin bloqueo), devuelve 0 con éxito 
/* La función sem_wait se utiliza para detectar si hay recursos disponibles para el especificado semáforo. Si no hay recursos disponibles, se bloqueará. Espere, si hay recursos disponibles, la operación sem - 1 se realizará automáticamente. 
   La función sem_post liberará los recursos del semáforo especificado y realizará la operación sem + 1. 
   Es decir, la aplicación y liberación de semáforos*/
int sem_timedwait (sem_t * sem,const struct timespec * abstime); // Espera hasta abstime segundos durante el período de espera/bloqueo de sem_wait y devuelve 
int sem_post_multiple (sem_t * sem,int count); // Aumenta el recuento de semáforos una vez ( Sin bloqueo) 
int
sem_getvalue(sem_t * sem, int * sval); // Obtener el valor del semáforo actual

Rutinas: Referencia pthread库-线程编程例程-来自百问网\01_文档配套源码\Pthread_Text12.c, pthread库-线程编程例程-来自百问网\02_视频配套源码\pthread3.c 和 pthread4.c.

mecanismo de variable de condición

Una variable de condición es un mecanismo de sincronización que se utiliza para notificar a otros subprocesos que se cumple una condición, es decir, un subproceso se suspende hasta que ocurre un evento. Generalmente se usa para notificar a la otra parte sobre la información de estado de los datos compartidos, por lo que las variables de condición se usan junto con mutex.

Las variables de condición son un método comúnmente utilizado para implementar la lógica de "esperar--->despertar" en programas multiproceso.

Archivo de encabezado: #include <pthread.h>.

API de uso común:

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); // Inicializa la variable de condición 
/* Si no es necesario establecer ningún atributo, completa cond_attr como N ULL */ / 
* Otra forma de inicializar pthread_cond_t de forma predeterminada cond = PTHREAD_COND_INITIALIZER; */ 
int pthread_cond_destroy (pthread_cond_t *cond); // Destruye la variable de condición 
int
Pthread_wait (pthread_cond_t *cond, pthread_mutex_t *mutex); t PTHread_Cond_Timewait (pthread_cond_t *cond, pthread_mutex_t *mutex 
, const struct timeSpec *TSPTR); // Dentro de un tiempo determinado, espere a que la variable de condición se vuelva verdadera 
int
pthread_cond_signal(pthread_cond_t *cond); // Notifica a la variable de condición que se cumple la condición. La función pthread_cond_signal solo despertará un hilo esperando la variable de condición cond. Utilice: Enviar 
señal
 
    :
     
    pthread_mutex_lock (&mutex); bloquear primero
        Modifique el recurso crítico aquí, luego modifique el recurso 
    pthread_cond_signal(&cond); luego envíe la señal de la variable de condición 
    pthread_mutex_unlock(&mutex); luego desbloquee 
y espere
    la señal: 
    pthread_mutex_lock(&mutex); bloquear primero/* bloquear cuando pueda bloquear y luego ejecutar más tarde, de lo contrario bloquear */ 
    pthread_cond_wait(&cond, &mutex); y luego esperar la señal de la variable de condición/* Si no se cumple la condición, el mutex se desbloqueará primero y luego se bloqueará/dormirá aquí para esperar a que se cumpla la condición. se cumplirá; cuando se cumpla la condición, se despertará, primero bloqueará el mutex y luego desbloqueará y ejecutará más tarde*/ 
        Modifique el recurso crítico aquí, luego modifique el recurso 
    pthread_mutex_unlock(&mutex); y luego desbloquee 
    ...

Rutina: Referencia pthread库-线程编程例程-来自百问网\02_视频配套源码\pthread5.c.

Señal

El manejo de señales entre múltiples subprocesos en un proceso es diferente del procesamiento de señales en el proceso: requiere más aprendizaje y uso.

Por ejemplo, cuando envía una señal SIGSTP a un proceso, todos los subprocesos del proceso se detendrán. Debido a que todos los subprocesos usan el mismo espacio de memoria, los controladores para una señal son los mismos, pero diferentes subprocesos tienen diferentes estructuras de administración, por lo que diferentes subprocesos pueden tener diferentes máscaras (citado de Linux Thread Implementation & LinuxThread vs. NPTL & kernel a nivel de usuario) . Hilos de nivel e hilos y procesamiento de señales - blcblc - Blog Park (cnblogs.com) ).

referencia:

Supongo que te gusta

Origin blog.csdn.net/Staokgo/article/details/132630769
Recomendado
Clasificación