Compartir tecnología

Multiproceso y subprocesos múltiples de Linux (8) Subprocesos múltiples

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

subprocesos múltiples

Definición del hilo

Un hilo es una unidad de ejecución en un proceso.

Responsable de la ejecución de programas en el proceso actual,

Hay al menos un hilo en un proceso.

Puede haber varios hilos en un proceso.

Varios subprocesos comparten todos los recursos del mismo proceso y cada subproceso participa en la programación unificada del sistema operativo.

Puede entenderse simplemente como proceso = recursos de memoria + hilo principal + hilo secundario +…

Hilos y procesos

Para tareas estrechamente relacionadas, se prefiere el subproceso múltiple durante la concurrencia. Para tareas que no están estrechamente relacionadas y son relativamente independientes, se recomienda elegir multiproceso;

  • Proceso: la unidad básica de asignación de recursos por parte del sistema operativo. Es la unidad más pequeña de asignación de recursos, la unidad de ejecución y programación del programa y la instancia en ejecución del programa.
  • Subproceso: es la unidad básica de programación y despacho de CPU, la unidad más pequeña de ejecución de CPU, la unidad más pequeña de flujo de ejecución de programa y la unidad más pequeña de ejecución de programa.

La diferencia entre subprocesos y procesos:

  • espacio de memoria
    • Varios subprocesos en un proceso comparten el mismo espacio de memoria
    • Múltiples procesos tienen espacio de memoria independiente.
  • Comunicación entre procesos/hilos.
    • Comunicación simple entre hilos.
    • La comunicación entre procesos es compleja

Recursos del hilo

  • Recursos de proceso compartidos
    • mismo espacio de direcciones
    • tabla de descriptores de archivos
    • Cómo se procesa cada señal
    • directorio de trabajo actual
    • ID de usuario e ID de grupo
  • Recursos únicos
    • Pila de hilos
    • Cada hilo tiene información de contexto privada.
    • ID del hilo
    • Valor de registro
    • valor de error
    • Palabras de máscara de señal y prioridades de programación

Comandos relacionados con hilos

Hay muchos comandos para ver el proceso en el sistema Linux, incluidos pidstat, top, ps, puede ver el proceso y también puede ver un
hilo en proceso

comando pidstat

Después de instalar la herramienta sysstat en ubuntu, se puede admitir pidstat.

sudo apt install sysstat

Opciones

-t: muestra los subprocesos asociados con el proceso especificado

-p: especifica el pid del proceso

Ejemplo

Ver los hilos asociados con el proceso 12345

sudo pidstat -t -p 12345

Ver hilos asociados con todos los procesos.

sudo pidstat -t

Ver los subprocesos asociados con el proceso 12345, salida cada 1 segundo

sudo pidstat -t -p 12345 1

Ver los subprocesos asociados con todos los procesos, generar cada 1 segundo

sudo pidstat -t 1

comando superior

Utilice el comando superior para ver los subprocesos de un determinado proceso. Debe utilizar la opción -H en combinación con -p para especificar el pid.

Opciones

-H: Mostrar información del hilo

-p: especifica el pid del proceso

Ejemplo

Ver los hilos asociados con el proceso 12345

sudo top -H -p 12345

Ver hilos asociados con todos los procesos.

sudo top -H

comando ps

Utilice el comando ps combinado con la opción -T para ver todos los subprocesos de un proceso

Opciones

-T: Mostrar información del hilo

-p: especifica el pid del proceso

Ejemplo

Ver los hilos asociados con el proceso 12345

sudo ps -T -p 12345

Ver hilos asociados con todos los procesos.

sudo ps -T

Escenarios de concurrencia comunes

1. Modo multiproceso

En el modo multiproceso, cada proceso es responsable de diferentes tareas sin interferir entre sí. Cada proceso se ejecuta en un espacio de memoria diferente sin afectarse entre sí.

  • ventaja:
    • El espacio de direcciones del proceso es independiente. Una vez que ocurre una excepción en un proceso, no afectará a otros procesos.
  • defecto:
    • Cada proceso necesita asignar espacio de memoria independiente. Crear un proceso es costoso y ocupa más memoria.
    • La colaboración entre procesos y la comunicación entre procesos son más complejas.
  • Escena aplicable:
    • Varias tareas no están muy relacionadas, por lo que se puede utilizar el modo multiproceso
    • No hay dependencias entre tareas y se puede utilizar el modo multiproceso

2. Modo multiproceso

En el modo de subprocesos múltiples, puede haber varios subprocesos en un proceso, compartiendo el mismo espacio de memoria, y los subprocesos pueden comunicarse directamente.

  • ventaja:
    • La comunicación entre hilos es simple.
    • Varios subprocesos del mismo proceso pueden compartir recursos y mejorar la utilización de los recursos.
  • defecto:
    • Los subprocesos no tienen espacios de direcciones de proceso independientes. Una vez que sale el subproceso principal, también saldrán otros subprocesos.
    • El cambio y la programación de subprocesos requieren recursos. Demasiados subprocesos consumirán recursos del sistema.
    • La sincronización entre subprocesos es compleja y se deben considerar los problemas de seguridad de los subprocesos.
  • Escena aplicable:
    • Existen dependencias entre tareas y se puede utilizar el modo multiproceso.
    • La comunicación entre tareas es relativamente frecuente y se puede utilizar el modo multiproceso.

Crear hilo

1. pthread_create()

pthread_create() se utiliza para crear un hilo. Una vez que la creación es exitosa, el hilo comienza a ejecutarse.
Después de llamar exitosamente a pthread_create(), devolverá 0; de lo contrario, devolverá un código de error.

Archivo de encabezado de función:

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
  • 1
  • 2
  • 3
  • 4

Descripción de parámetros:

  • hilo: puntero al tipo pthread_t, utilizado para almacenar la ID del hilo.
  • attr: atributo del hilo, que puede ser NULL, lo que indica que se utiliza el atributo predeterminado.
  • start_routine: Función de entrada del hilo.
  • arg: parámetros pasados ​​a la función de entrada del hilo.

valor de retorno:

  • 0: Creado con éxito.
  • EAGAIN: Recursos insuficientes, falló la creación del hilo.
  • EINVAL: El parámetro no es válido.
  • ENOMEM: memoria insuficiente, falló la creación del hilo.

Aviso:

  • Una vez que el subproceso secundario se crea con éxito, se programará de forma independiente para su ejecución y se ejecutará al mismo tiempo que otros subprocesos.
  • La biblioteca -lpthread debe estar vinculada al compilar.

Ejemplo: crear un hilo

// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    printf("%sn",(char *)arg);
}

int main() {
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //@param tid 线程ID
    //@param attr 线程属性
    //@param start_routine 线程函数
    //@param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
        printf("pthread_create error!n");
        return 1;
    }
    sleep(1); // 等待线程执行完毕
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2. pthread_exit() sale del hilo

pthread_exit() se utiliza para salir del hilo. Una vez que el hilo completa la ejecución, se llamará automáticamente a pthread_exit() para salir.

Archivo de encabezado de función:

#include <pthread.h>

void pthread_exit(void *retval);
  • 1
  • 2
  • 3

Descripción de parámetros:

  • retval: el valor devuelto cuando sale el hilo.
  • Después de ejecutar la función del subproceso, se llamará automáticamente a pthread_exit () para salir.

3. pthread_join() espera a que finalice el hilo

pthread_join() se usa para esperar a que finalice el hilo,
Después de llamar a pthread_join (), el hilo actual se bloqueará hasta que finalice.

Archivo de encabezado de función:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
  • 1
  • 2
  • 3

Descripción de parámetros:

  • hilo: ID del hilo.
  • retval: puntero al valor de retorno del hilo, utilizado para almacenar el valor devuelto cuando el hilo sale. (puntero secundario)

valor de retorno:

  • 0: Espere hasta que tenga éxito.
  • EINVAL: El parámetro no es válido.
  • ESRCH: el ID del hilo no existe.
  • EDEADLK: El hilo está en un estado de punto muerto.

Ejemplo:

// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    sleep(1); // 休眠1秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}

int main() {
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
        printf("pthread_create error!n");
        return 1;
    }

    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid, NULL);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
等待线程结束...
Hello, World!
  • 1
  • 2

Separación de hilos

Los hilos se dividen en combinables y desmontables.

  • Se puede combinar
    • Un subproceso combinable puede reclamar sus recursos y ser eliminado por otros subprocesos; sus recursos de memoria (como la pila) no se liberan hasta que otros subprocesos lo reclaman.
    • El estado predeterminado de creación de subprocesos se puede combinar. Otros subprocesos pueden llamar a la función pthread_join para esperar a que el subproceso secundario salga y libere los recursos relacionados.
  • separable
    • Otros subprocesos no pueden reciclarlo ni eliminarlo. El sistema libera los recursos del subproceso cuando finaliza.
// todo : 创建一个线程,并在线程中打印出“Hello, World!”
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    sleep(1); // 休眠1秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}

int main() {
    pthread_t tid; //? typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int ret = pthread_create(&tid, NULL,print_hello, "Hello, World!");
    if (ret!= 0){
        printf("pthread_create error!n");
        return 1;
    }

    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    //pthread_join(tid, NULL);//! 阻塞等待线程结束,直到线程结束后才继续往下执行

    //线程分离
    pthread_detach(tid); //! 分离线程,不用等待线程结束后才退出程序,该线程的资源在它终止时由系统来释放。

    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

Crear múltiples hilos

Ejemplo 1: crear varios subprocesos para realizar diferentes tareas

// todo : 创建多个线程,执行不同的任务
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello_A(void *arg) {
    sleep(1); // 休眠1秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}
// 线程函数
//@param arg 线程函数参数
void * print_hello_B(void *arg) {
    sleep(2); // 休眠2秒
    printf("%sn",(char *)arg);
    pthread_exit(NULL); // 线程退出
}


int main() {
    pthread_t tidA; //? 存储线程ID  typedef unsigned long int pthread_t;
    pthread_t tidB;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int retA = pthread_create(&tidA, NULL,print_hello_A, "A_ Hello, World!");
    if (retA!= 0){
        printf("pthread_create error!n");
        return 1;
    }

    int retB = pthread_create(&tidB, NULL,print_hello_B, "B_ Hello, World!");
    if (retB!= 0){
        printf("pthread_create error!n");
        return 1;
    }
    
    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tidA, NULL);//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tidB, NULL);
    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

Ejemplo 2: crear varios subprocesos para realizar la misma tarea

// todo : 创建多个线程,执行相同任务
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
//? 两个线程执行相同任务,对函数中的值修改了,会不会影响到其他线程的执行?
//! 在多线程编程中,如果多个线程执行相同的任务并且对共享资源进行修改,可能会影响到其他线程的执行。
//! 这是因为多个线程共享相同的内存空间,对共享资源的修改可能会导致竞态条件(race condition),
//! 从而导致不可预测的行为。
//! print_hello函数中的变量i是局部变量,每个线程都会有自己的i副本,因此对i的修改不会影响到其他线程。
//! 但是,如果涉及到共享资源(例如全局变量或静态变量),就需要考虑线程同步的问题,以避免竞态条件。


//*局部变量:每个线程都有自己的栈空间,因此局部变量是线程私有的,不会影响到其他线程。
//*共享资源:如果多个线程访问和修改同一个全局变量或静态变量,就需要使用同步机制(如互斥锁、信号量等)来确保线程安全。
//Linux:在Linux系统中,默认的线程栈大小通常是8MB。可以使用ulimit -s命令查看和修改当前用户的线程栈大小。例如,ulimit -s 1024将线程栈大小设置为1MB。
//Windows:在Windows系统中,默认的线程栈大小是1MB。可以通过编译器选项或在创建线程时指定栈大小来修改。

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {

    for (char i = 'a'; i < 'z' ; ++i) {
        printf("%cn", i);
        sleep(1); // 休眠1秒
    }
    pthread_exit(NULL); // 线程退出
}

int main() {
    pthread_t tid[2]={0}; //? 存储线程ID的数组  typedef unsigned long int pthread_t;


    for (int i = 0; i < 2; ++i) {
        // 创建线程
        //* @param tid 线程ID
        //* @param attr 线程属性
        //* @param start_routine 线程函数
        //* @param arg 线程函数参数
        int retA = pthread_create(&tid[i], NULL,print_hello, NULL);
        if (retA!= 0){
            printf("pthread_create error!n");
            return 1;
        }
    }

    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid[0], NULL);//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tid[1], NULL);


    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

Comunicación entre hilos

Otra comunicación entre procesos también se aplica a la comunicación entre subprocesos.

El hilo principal pasa parámetros al hilo secundario.

Al crear un subproceso secundario a través de la función pthread_create(), el cuarto parámetro de pthread_create() es el parámetro pasado a la función del subproceso secundario.

El subproceso pasa parámetros al hilo principal

Al salir del hilo secundario a través de la función pthread_exit(), puede pasar parámetros al hilo principal.

void pth_exit(void *retval);
  • 1

Cuando espere el final del subproceso a través de la función pthread_join (), obtenga los parámetros de retorno del subproceso.

int pthread_join (pthread_t __th, void **__thread_return);
//二级指针获取到了pthread_exit()函数参数指针的指向地址,通过该地址可以获取到子线程的返回参数。
  • 1
  • 2

Ejemplo:

// todo : 线程直接通讯,子线程向父线程传参
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    printf("子线程开始,结束之时传递参数100的地址n");

    sleep(1); // 休眠1秒
    //! int num=100;//局部变量,函数结束释放内存
    static int num=100;//* 静态局部变量,函数结束不释放内存,延长生命周期
    pthread_exit(&num); // 线程退出
}



int main() {
    pthread_t tid; //? 存储线程ID  typedef unsigned long int pthread_t;
    // 创建线程
    //* @param tid 线程ID
    //* @param attr 线程属性
    //* @param start_routine 线程函数
    //* @param arg 线程函数参数
    int retA = pthread_create(&tid, NULL,print_hello, NULL);
    if (retA!= 0){
        printf("pthread_create error!n");
        return 1;
    }



    printf("等待线程结束...n");
    void* num;//获取子进程传递的参数,num指向了子进程传递的参数
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid, (void **)&num);//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    printf("子线程结束,传递的参数为%dn",*(int*)num);
    printf("主线程结束n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

exclusión mutua de subprocesos

exclusión mutua de subprocesos

Mutex es un mecanismo de sincronización utilizado para controlar el acceso a recursos compartidos.

La principal ventaja de los hilos es la capacidad de compartir información a través de variables globales, pero este conveniente intercambio tiene un costo:

Se debe garantizar que varios subprocesos no modifiquen la misma variable al mismo tiempo.

Un hilo no leerá las variables modificadas por otros hilos. De hecho, dos hilos no pueden acceder a la sección crítica al mismo tiempo.

El principio del bloqueo mutex

El principio de un bloqueo mutex es que cuando un subproceso intenta ingresar a un área mutex, si el área mutex ya está ocupada por otros subprocesos, el subproceso se bloqueará hasta que se libere el área mutex.

Es esencialmente una variable de tipo pthread_mutex_t, que contiene un valor entero para representar el estado del área mutex.
Cuando el valor es 1, significa que el recurso crítico actual puede competir por el acceso y el subproceso que obtiene el bloqueo mutex puede ingresar a la sección crítica. En este momento el valor es 0 y otros subprocesos solo pueden esperar.
Cuando el valor es 0, significa que el recurso crítico actual está ocupado por otros subprocesos y no puede ingresar a la sección crítica y solo puede esperar.

Características de las cerraduras mutex

typedef union
{
  struct __pthread_mutex_s __data; // 互斥锁的结构体
  char __size[__SIZEOF_PTHREAD_MUTEX_T];// 互斥锁的大小
  long int __align;// 互斥锁的对齐
} pthread_mutex_t;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • El bloqueo mutex es una variable de tipo pthread_mutex_t, que representa un bloqueo mutex.
  • Si dos subprocesos acceden a la misma variable pthread_mutex_t, entonces acceden al mismo bloqueo mutex
  • Las variables correspondientes se definen en el archivo de encabezado pthreadtypes.h, que es un cuerpo común que contiene una estructura.

Uso de bloqueos mutex

Hay dos formas principales de inicializar bloqueos mutex de subprocesos:

inicialización estática

  • Defina una variable de tipo pthread_mutex_t e inicialícela en PTHREAD_MUTEX_INITIALIZER.
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
  • 1

inicialización dinámica

Inicialización dinámica La inicialización dinámica implica principalmente dos funciones: función pthread_mutex_init y función pthread_mutex_destroy

función pthread_mutex_init()

Se utiliza para inicializar un bloqueo mutex y acepta dos parámetros: la dirección del bloqueo mutex y los atributos del bloqueo mutex.

Archivo de encabezado de función:

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • 1
  • 2
  • 3

Descripción de parámetros:

  • mutex: puntero al tipo pthread_mutex_t, utilizado para almacenar la dirección del bloqueo mutex.
  • attr: el atributo del bloqueo mutex, que puede ser NULL, lo que indica que se utiliza el atributo predeterminado.

valor de retorno:

  • 0: Inicialización exitosa.
  • En caso de falla, se devuelve un código de error.
función pthread_mutex_destroy()

Se utiliza para destruir el bloqueo mutex y acepta un parámetro: la dirección del bloqueo mutex.

Archivo de encabezado de función:

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 1
  • 2
  • 3

Descripción de parámetros:

  • mutex: puntero al tipo pthread_mutex_t, utilizado para almacenar la dirección del bloqueo mutex.

valor de retorno:

  • 0: Destrucción exitosa.
  • En caso de falla, se devuelve un código de error.

Ejemplo:

// todo :  互斥锁;创建两个线程,分别对全局变量进⾏ +1 操作
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>


static int global = 0;// 全局变量

//静态初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 互斥锁
//动态初始化互斥锁
pthread_mutex_t mut;// 互斥锁

// 线程函数
//@param arg 线程函数参数
void * print_hello(void *arg) {
    printf("子线程开始n");

    int loops = *(int *)arg;
    int i,tmp = 0;
    for (i = 0;i < loops;i++){
        pthread_mutex_lock(&mut);// 加锁
        printf("子线程%d,global=%dn",i,global);
        tmp = global;
        tmp++;
        global = tmp;
        pthread_mutex_unlock(&mut);// 解锁
    }
    printf("子线程结束n");
    pthread_exit(NULL); // 线程退出
}



int main() {

    // 动态初始化互斥锁
    //* @param mutex 互斥锁
    //* @param attr 互斥锁属性 NULL 是默认属性
    int r= pthread_mutex_init(&mut,NULL);
    if (r!= 0){
        printf("pthread_mutex_init error!n");
        return 1;
    }

    pthread_t tid[2]={0}; //? 存储线程ID  typedef unsigned long int pthread_t;
    int arg=20;
    for (int i = 0; i < 2; i++){
        // 创建线程
        //* @param tid 线程ID
        //* @param attr 线程属性
        //* @param start_routine 线程函数
        //* @param arg 线程函数参数
        int retA = pthread_create(&tid[i], NULL,print_hello, &arg);
        if (retA!= 0){
            printf("pthread_create error!n");
            return 1;
        }
    }




    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid[0],NULL );//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tid[1],NULL );

    printf("%dn",global);
    printf("主线程结束n");

    // 销毁动态创建的互斥锁
    //* @param mutex 互斥锁
    pthread_mutex_destroy(&mut);// 销毁互斥锁

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

Sincronización de hilos

Sincronización de subprocesos: se refiere al acceso ordenado a los recursos por parte de los visitantes a través de otros mecanismos sobre la base de exclusión mutua (en la mayoría de los casos).

Variable de condición: un mecanismo proporcionado por la biblioteca de subprocesos específicamente para la sincronización de subprocesos.

Un escenario de aplicación típico para la sincronización de subprocesos es entre productores y consumidores.

Problemas de productores y consumidores.

En este modelo, se divide en subproceso productor y subproceso consumidor, y el proceso de sincronización de múltiples subprocesos se simula a través de este subproceso.

En este modelo, se requieren los siguientes componentes:

  • Almacén: utilizado para almacenar productos, generalmente como un recurso compartido.
  • Hilo de productor: utilizado para producir productos.
  • Hilo de consumo: utilizado para consumir productos.

principio:

Cuando no hay ningún producto en el almacén, el hilo del consumidor debe esperar hasta que haya un producto antes de poder consumirlo.

Cuando el almacén está lleno de productos, el hilo productor debe esperar hasta que el hilo consumidor consuma los productos.

Ejemplo de implementación de modelos de productor y consumidor basados ​​en bloqueos mutex

El hilo conductor es el consumidor.

n subprocesos como productores

// todo :  基于互斥锁实现⽣产者与消费者模型主线程为消费者,n 个⼦线程作为⽣产者
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
static int n = 0; // 产品数量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 互斥锁

//生产者执行函数
void * dofunc(void *arg) {
    int arg1 = *(int*)arg;
    for (int i = 0; i <arg1; i++) {
        //获取互斥锁
        pthread_mutex_lock(&mutex);
        //生产产品
        printf("生产者%ld生产了%d个产品n",pthread_self(),++n);//! pthread_self()返回当前线程ID
        //释放互斥锁
        pthread_mutex_unlock(&mutex);
        //休眠1秒
        sleep(1);
    }
    pthread_exit(NULL);
}


int main() {
    pthread_t tid[4]={0}; //? 存储线程ID  typedef unsigned long int pthread_t;
    int arr[4]={1,2,3,4};
    for (int i = 0; i < 4; i++) {
        // 创建线程
        //* @param tid 线程ID
        //* @param attr 线程属性
        //* @param start_routine 线程函数
        //* @param arg 线程函数参数
        int retA = pthread_create(&tid[i], NULL,dofunc,&arr[i] );
        if (retA!= 0){
            printf("pthread_create error!n");
            return 1;
        }
    }
    //消费者执行

    for (int i = 0;i<10;i++) {
        //获取互斥锁
        pthread_mutex_lock(&mutex);
        while (n > 0){
            //消费产品
            printf("消费者%ld消费了1个产品:%dn",pthread_self(),n--);
        }
        //释放互斥锁
        pthread_mutex_unlock(&mutex);
        //休眠1秒
        sleep(1);
    }


    printf("等待线程结束...n");
    // 等待线程结束
    //* @param thread 线程ID
    //* @param status 线程退出状态
    pthread_join(tid[0],NULL );//! 阻塞等待线程结束,直到线程结束后才继续往下执行
    pthread_join(tid[1],NULL );
    pthread_join(tid[2],NULL );
    pthread_join(tid[3],NULL );

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

variable de condición

Una variable de condición es un mecanismo de sincronización que permite que un subproceso espere a que se cumpla una determinada condición antes de continuar ejecutándose.

El principio de una variable de condición es que contiene un bloqueo mutex y una cola de espera.

Los bloqueos Mutex se utilizan para proteger colas de espera y variables de condición.

Insertar descripción de la imagen aquí

Inicialización de variable de condición

La naturaleza de la variable de condición es del tipo pthread_cond_t

其他线程可以阻塞在这个条件变量上, 或者唤
醒阻塞在这个条件变量上的线程
typedef union
{
  struct __pthread_cond_s __data;
  char __size[__SIZEOF_PTHREAD_COND_T];
  __extension__ long long int __align;
} pthread_cond_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

La inicialización de variables de condición se divide en inicialización estática e inicialización dinámica.

inicialización estática

Para variables de condición inicializadas estáticamente, primero debe definir una variable de tipo pthread_cond_t y luego inicializarla en PTHREAD_COND_INITIALIZER.

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 1

Inicialización dinámica pthread_cond_init()

Para inicializar dinámicamente una variable de condición, primero debe definir una variable de tipo pthread_cond_t y luego llamar a la función pthread_cond_init para inicializarla.

Archivo de encabezado de función:

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
  • 1
  • 2
  • 3

Descripción de parámetros:

  • cond: puntero al tipo pthread_cond_t, utilizado para almacenar la dirección de la variable de condición.
  • attr: atributo de la variable de condición, que puede ser NULL para usar el atributo predeterminado.

valor de retorno:

  • 0: Inicialización exitosa.
  • En caso de falla, se devuelve un código de error.

pthread_cond_destroy()

Utilizado para destruir variables de condición, acepta un parámetro: la dirección de la variable de condición.

Archivo de encabezado de función:

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);
  • 1
  • 2
  • 3

Descripción de parámetros:

  • cond: puntero al tipo pthread_cond_t, utilizado para almacenar la dirección de la variable de condición.

valor de retorno:

  • 0: Destrucción exitosa.
  • En caso de falla, se devuelve un código de error.

Uso de variables de condición.

El uso de variables de condición se divide en espera y notificación.

Espere pthread_cond_wait()

La función de espera pthread_cond_wait() acepta tres parámetros: la dirección de la variable de condición, la dirección del bloqueo mutex y el tiempo de espera.

Archivo de encabezado de función:

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
  • 1
  • 2
  • 3

Descripción de parámetros:

  • cond: puntero al tipo pthread_cond_t, utilizado para almacenar la dirección de la variable de condición.
  • mutex: puntero al tipo pthread_mutex_t, utilizado para almacenar la dirección del bloqueo mutex.
  • abstime: tiempo de espera, que puede ser NULL, lo que indica que no hay tiempo de espera.

valor de retorno:

  • 0: Espere hasta que tenga éxito.
  • En caso de falla, se devuelve un código de error.

Notificar a pthread_cond_signal()

función de notificación
pthread_cond_signal() acepta un parámetro: la dirección de la variable de condición.

Archivo de encabezado de función:

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
  • 1
  • 2
  • 3

Descripción de parámetros:

  • cond: puntero al tipo pthread_cond_t, utilizado para almacenar la dirección de la variable de condición.

valor de retorno:

  • 0: Notificación exitosa.
  • En caso de falla, se devuelve un código de error.

Notificar a todos pthread_cond_broadcast()

Notificar a todas las funciones
pthread_cond_broadcast() acepta un parámetro: la dirección de la variable de condición.

Archivo de encabezado de función:

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);
  • 1
  • 2
  • 3

Descripción de parámetros:

  • cond: puntero al tipo pthread_cond_t, utilizado para almacenar la dirección de la variable de condición.

valor de retorno:

  • 0: Notificación exitosa.
  • En caso de falla, se devuelve un código de error.

Ejemplo de implementación de modelos de productor y consumidor basados ​​en variables condicionales

Insertar descripción de la imagen aquí

step 1 : 消费者线程判断消费条件是否满足 (仓库是否有产品),如果有产品可以消费,则可以正
常消费产品,然后解锁
step 2 : 当条件不能满足时 (仓库产品数量为 0),则调用 pthread_cond_wait 函数, 这个函数
            具体做的事情如下:
            在线程睡眠之前,对互斥锁解锁
            让线程进⼊到睡眠状态
            等待条件变量收到信号时 唤醒,该函数重新竞争锁,并获取锁后,函数返回 
step 3 :重新判断条件是否满足, 如果不满足,则继续调用 pthread_cond_wait 函数
step 4 : 唤醒后,从 pthread_cond_wait 返回,消费条件满足,则正常消费产品
step 5 : 释放锁,整个过程结束
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

¿Por qué es necesario utilizar variables de condición junto con bloqueos mutex?

Proteger los datos compartidos:

Los bloqueos Mutex se utilizan para proteger los datos compartidos y garantizar que solo un subproceso pueda acceder y modificar los datos al mismo tiempo.

Esto evita carreras de datos e inconsistencias.

Las variables de condición se utilizan para la comunicación entre subprocesos para notificar a otros subprocesos que se ha cumplido una determinada condición.

Sin embargo, la operación de las variables de condición no proporciona en sí misma protección para los datos compartidos y, por lo tanto, debe usarse junto con un bloqueo mutex.

Evite falsos despertares:

Una característica de las variables de condición es que pueden ocurrir despertares espurios.

Es decir, el hilo se activa sin notificación explícita. Para evitar operaciones incorrectas causadas por esta situación,

El hilo debe volver a verificar si la condición realmente se cumple después de despertarse.

El uso de un mutex garantiza que otros subprocesos no modifiquen los datos compartidos mientras se verifican las condiciones, evitando así errores causados ​​por reactivaciones espurias.

Asegúrese de que la notificación sea correcta:

Cuando un hilo notifica a otros hilos a través de variables de condición, debe asegurarse de que los datos compartidos se hayan actualizado antes de notificar.

Un mutex garantiza esto, asegurando que todas las operaciones de actualización de datos se hayan completado antes de liberar el bloqueo.

Del mismo modo, el hilo que recibe la notificación debe mantener el mutex antes de verificar la condición para garantizar que los datos sean estables cuando se verifique la condición.

Implemente patrones de sincronización complejos:
La combinación de bloqueos mutex con variables de condición puede implementar patrones de sincronización más complejos, como problemas de productor-consumidor, problemas de lector-escritor, etc. Los mutex protegen los datos compartidos y las variables de condición se utilizan para la coordinación y comunicación entre subprocesos.

// todo :  条件变量
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>


static int number = 0;// 产品数量
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;// 互斥锁
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;// 条件变量

// 线程函数
//@param arg 线程函数参数
void * thread_handler(void *arg) {
    int cnt = atoi((char *)arg);// 获取线程参数
    int i,tmp;// 临时变量
    for(i = 0;i < cnt;i++){// 生产产品
        pthread_mutex_lock(&mtx);// 上锁
        printf("线程 [%ld] ⽣产⼀个产品,产品数量为:%dn",pthread_self(),++number);
        pthread_mutex_unlock(&mtx);// 解锁

        //! 唤醒cond阻塞的线程
        //! @param cond 条件变量
        //pthread_cond_signal(&cond);//! 只能唤醒一个线程,如果有多个线程在等待,则只有一个线程会被唤醒
        //唤醒所有线程
        pthread_cond_broadcast(&cond);
    }
    pthread_exit((void *)0);// 线程退出
}



int main(int argc,char *argv[]) {

    pthread_t tid[argc-1];// 线程ID
    int i;
    int err;
    int total_of_produce = 0;// 总共生产的产品数量
    int total_of_consume = 0;// 总共消费的产品数量
    bool done = false;// 是否完成生产
    //循环创建线程
    for (i = 1;i < argc;i++){
        total_of_produce += atoi(argv[i]);// 计算总共需要生产的产品数量
        // 创建线程
        err = pthread_create(&tid[i-1],NULL,thread_handler,(void *)argv[i]);
        if (err != 0){
            perror("[ERROR] pthread_create(): ");
            exit(EXIT_FAILURE);
        }
    }
    //消费者
    for (;;){
        //*先获取锁,再进行条件变量的等待
        pthread_mutex_lock(&mtx);// 上锁

        //*while循环来判断条件,避免虚假唤醒
        while(number == 0) {// 等待生产者生产产品
            //! 等待条件变量
            //! @param cond 条件变量
            //! @param mtx 互斥锁
            //! 函数中会释放互斥锁,并阻塞线程,
            //! 直到条件变量被唤醒,再重新竞争互斥锁,获取互斥锁并继续执行
            pthread_cond_wait(&cond, &mtx);
        }
        while(number > 0){
            total_of_consume++;// 总共消费的产品数量
            printf("消费⼀个产品,产品数量为:%dn",--number);// 消费产品
            done = total_of_consume >= total_of_produce;// 是否完成生产
        }
        pthread_mutex_unlock(&mtx);// 解锁

        if (done)// 是否完成生产
            break;
    }

    // 等待线程退出
    for(i = 0;i < argc-1;i++){
        pthread_join(tid[i],NULL);
    }

    return 0;

}
    //循环创建线程
    for (i = 1;i < argc;i++){
        total_of_produce += atoi(argv[i]);// 计算总共需要生产的产品数量
        // 创建线程
        err = pthread_create(&tid[i-1],NULL,thread_handler,(void *)argv[i]);
        if (err != 0){
            perror("[ERROR] pthread_create(): ");
            exit(EXIT_FAILURE);
        }
    }
    //消费者
    for (;;){
        //*先获取锁,再进行条件变量的等待
        pthread_mutex_lock(&mtx);// 上锁

        //*while循环来判断条件,避免虚假唤醒
        while(number == 0) {// 等待生产者生产产品
            //! 等待条件变量
            //! @param cond 条件变量
            //! @param mtx 互斥锁
            //! 函数中会释放互斥锁,并阻塞线程,
            //! 直到条件变量被唤醒,再重新竞争互斥锁,获取互斥锁并继续执行
            pthread_cond_wait(&cond, &mtx);
        }
        while(number > 0){
            total_of_consume++;// 总共消费的产品数量
            printf("消费⼀个产品,产品数量为:%dn",--number);// 消费产品
            done = total_of_consume >= total_of_produce;// 是否完成生产
        }
        pthread_mutex_unlock(&mtx);// 解锁

        if (done)// 是否完成生产
            break;
    }

    // 等待线程退出
    for(i = 0;i < argc-1;i++){
        pthread_join(tid[i],NULL);
    }

    return 0;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127