Un artículo que explica la programación de subprocesos de Linux: principios de subprocesos, programación de subprocesos, etc., con ejemplos completos.

Tabla de contenido

Hilos de Linux

Introducción a cada prototipo de API.

Rutinas que utilizan estas API


Hilos de Linux

Para un estudio detallado, puede utilizar un diccionario como referencia:

Subprocesos múltiples significa que se pueden ejecutar múltiples subprocesos diferentes simultáneamente en un solo programa/proceso (puede considerarse como múltiples tareas que comparten el mismo recurso de memoria) para realizar diferentes tareas:

  • Mayor eficiencia operativa, ejecución paralela;

  • Multithreading es un modelo de programación modular;

  • En comparación con los procesos, la sobrecarga de creación y conmutación de subprocesos es menor;

  • Fácil comunicación;

  • Puede simplificar la estructura del programa y facilitar la comprensión y el mantenimiento; mayor utilización de recursos.

Escenarios de aplicaciones multiproceso:

  1. Si hay operaciones que deben esperarse en el programa, como operaciones de red, E/S de archivos, etc., se pueden utilizar subprocesos múltiples para utilizar completamente los recursos del procesador sin bloquear la ejecución de otras tareas en el programa.

  2. Cuando hay grandes tareas descomponibles en el programa, como tareas informáticas de larga duración, se pueden utilizar subprocesos múltiples para completar las tareas juntas y acortar el tiempo de informática.

  3. Hay tareas en el programa que deben ejecutarse en segundo plano, como algunas tareas de monitoreo y tareas programadas, que se pueden completar mediante el uso de subprocesos múltiples.

Seguridad de subprocesos y sincronización de subprocesos:

Seguridad de subprocesos : Cuando se accede a subprocesos múltiples, se adopta un mecanismo de bloqueo. Cuando un subproceso accede a ciertos datos de esta clase, está protegido y otros subprocesos no pueden acceder a ellos hasta que el subproceso haya terminado de leer, y luego otros subprocesos pueden usarlo. No habrá inconsistencia ni contaminación de datos.

Subprocesos inseguros : no se proporciona protección de acceso a datos y es posible que varios subprocesos cambien los datos sucesivamente, lo que genera datos sucios. Si varios subprocesos leen y escriben variables compartidas al mismo tiempo, se producirán inconsistencias en los datos.

Los problemas de seguridad de los subprocesos son causados ​​por variables globales y variables estáticas .

Si solo hay operaciones de lectura para variables globales y variables estáticas en cada subproceso, pero no hay operaciones de escritura, en términos generales, esta variable global es segura para subprocesos; si varios subprocesos realizan operaciones de escritura al mismo tiempo, generalmente se debe considerar la sincronización de subprocesos. De lo contrario, esto puede afectar la seguridad del hilo.

Sincronización de subprocesos : es decir, cuando un subproceso está operando en la memoria, ningún otro subproceso puede operar en la dirección de memoria.Hasta que el subproceso complete la operación, otros subprocesos pueden operar en la dirección de memoria.

Subproceso asincrónico : al acceder a un recurso, accede simultáneamente a otros recursos mientras espera inactivo para implementar un mecanismo de subprocesos múltiples.

Sincronización: el subproceso A quiere solicitar un recurso, pero el subproceso B está utilizando este recurso. Debido al mecanismo de sincronización, el subproceso A no puede solicitarlo. ¿Qué debo hacer? El subproceso A solo puede esperar.

Asíncrono: el subproceso A quiere solicitar un recurso, pero el subproceso B está utilizando este recurso. Debido a que no existe un mecanismo de sincronización, el subproceso A aún puede solicitarlo y el subproceso A no necesita esperar.

Ventajas de la sincronización de subprocesos :

Beneficios: Resuelva el problema de seguridad del hilo.

Desventajas: siempre hay un bloqueo de juicio, lo que reduce la eficiencia.

Pero entre seguridad y eficiencia, la primera consideración es la seguridad.

El desarrollo de subprocesos múltiples en C bajo el sistema Linux utiliza una biblioteca de subprocesos llamada pthread; los subprocesos a nivel de kernel y los subprocesos a nivel de usuario se distinguen/establecen mediante diferentes parámetros pasados ​​a la API al crear subprocesos.

Porque pthread no es la biblioteca predeterminada del sistema Linux, sino la biblioteca de subprocesos estándar POSIX. Se utiliza como biblioteca en Linux, por lo que la opción de compilación debe agregar -lpthread (o -pthread) para vincular explícitamente la biblioteca. Ejemplo: gcc xxx.c -lpthread -o xxx.bin.

Introducción a cada prototipo de API.

  • pthread_self(): obtiene el ID del hilo.

    /* pthread_self()——Función para obtener el ID del hilo 
        #include <pthread.h> 
        pthread_t pthread_self(void); 
        Éxito: devolver el número del hilo 
    */ 
    #include <pthread.h> 
    #include <stdio.h> 
    int main() 
    { 
        pthread_t tid = pthread_self(); 
        printf("tid = %lu\n",(unsigned long)tid); 
        return 0; 
    }
  • pthread_create()——Creación de hilo.

    /* pthread_create()——Creación de hilo 
        #include <pthread.h> 
        int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);El primer 
        parámetro de esta función Es el puntero pthread_t, utilizado para guardar el número de hilo del nuevo hilo. 
        El segundo parámetro representa las propiedades del hilo y se puede pasar NULL para representar las propiedades predeterminadas. 
        El tercer parámetro es un puntero de función, que es la función ejecutada por el hilo. El valor de retorno de esta función es nulo* y los parámetros formales son nulos*. 
        El cuarto parámetro se representa como el parámetro pasado a la función de procesamiento del hilo. Si no se pasa, se puede completar con NULL. 
        Devuelve 0 en caso de éxito y un valor negativo en caso de fracaso. 
    */ 
    #include <pthread.h> 
    #include <stdio.h> 
    #include <unistd.h> 
    #include <errno.h> 
    void *fun(void *arg) { printf("pthread_New = %lu\n", (unsigned long)pthread_self()); } 
    int main() 
    { 
        pthread_t tid1;  
        int ret = pthread_create(&tid1,NULL,fun,NULL);
        ... Simplificado, se omite el manejo de errores
        
        /* tid_main es el ID del subproceso obtenido a través de pthread_self, y tid_new es el espacio señalado por tid después de ejecutar pthread_create con éxito*/ / 
        * Es decir, los resultados de impresión de tid1 y pthread_New deben ser consistentes*/ 
        printf("tid_main = %lu tid_new = %lu \n" ,(unsigned long)pthread_self(),(unsigned long)tid1); 
        /* Debido al orden de ejecución aleatorio de los subprocesos, no agregar el modo de suspensión puede hacer que el subproceso principal se ejecute primero, lo que provocará el final de el proceso y la incapacidad de ejecutar el subproceso secundario*/ /* En otras 
        palabras, si el subproceso principal se ejecuta aquí sin agregar suspensión, entonces el retorno finalizará directamente, luego la diversión del subproceso finalizará antes de ejecutar este proceso*/ 
        sleep(1 ); return 0; 
    } 
    /* 
    De hecho, se pueden crear subprocesos a través de pthread_create, el subproceso principal. El tid después de ejecutar pthread_create en el subproceso apunta al espacio numérico del subproceso, que es consistente con el número de subproceso impreso por el subproceso secundario a través de la función pthread_self . 
    En particular, cuando finaliza el hilo principal que acompaña al proceso, el hilo creado también finalizará inmediatamente y no continuará ejecutándose. Además, el orden de ejecución de los subprocesos creados es aleatoriamente competitivo y no hay garantía de qué subproceso se ejecutará primero. Puede comentar la función de suspensión en el código anterior y observar el fenómeno experimental. 
    */

    Pase los parámetros al crear el proceso:

    #include <pthread.h> 
    #include <stdio.h> 
    #include <unistd.h> 
    #include <errno.h> 
    void *fun1(void *arg){ printf("%s:arg = %d Dirección = % p\n",__FUNCTION__,*(int *)arg,arg); } 
    void *fun2(void *arg){ printf("%s:arg = %d Dirección = %p\n",__FUNCTION__,(int)(long)arg,arg); } 
    int principal() 
    { 
        pthread_t tid1,tid2; int a = 50; 
        int ret = pthread_create(&tid1,NULL,fun1,(void *)&a); /* 传入地址 */ 
        ... 简化,错误处理略
        ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a); /* 传入值 */ 
        ... 简化
        sleep(1); printf("%s:a = %d Agregar = %p \n",__FUNCTION__,a,&a); devolver 0; 
    }
  • pthread_exit/pthread_cancel y pthread_join/pthread_tryjoin_np - Salida del hilo.

    Hay tres situaciones de salida para subprocesos:

    • La primera es que el proceso finaliza y todos los subprocesos del proceso también finalizarán.

    • El segundo es salir activamente del hilo a través de la función pthread_exit().

    • Otros subprocesos llaman al tercer tipo pthread_cancel() para salir pasivamente.

    Con respecto al reciclaje de recursos después de la salida del hilo:

    Varios subprocesos en un proceso comparten segmentos de datos. Si un hilo se puede unir o no está separado, después de que el hilo sale, los recursos ocupados por el hilo saliente no se liberarán cuando el hilo finalice. Debe usar la función pthread_join/pthread_tryjoin_np para sincronizar y liberar los recursos, es decir , cuando el hilo Después del final, el hilo principal necesita usar la función pthread_join / pthread_tryjoin_np para reciclar los recursos del hilo y obtener los datos que deben devolverse después de que finaliza el hilo. Si un hilo no se puede unir o está en un estado desconectado, reciclará activamente los recursos después de que el hilo salga. No es necesario llamar a pthread_join/pthread_tryjoin_np en el hilo principal para reciclar los recursos del hilo. Por supuesto, cuando el hilo sale, no puede pasar parámetros. Se pueden configurar los que se pueden unir y no unir, lo que se analizará más adelante en la sección de atributos del hilo.

    Respecto a la salida del hilo/proceso principal:

    • En el hilo principal, si regresa a la función principal o llama a la función exit(), el hilo principal saldrá y todo el proceso terminará. En este momento, todos los hilos del proceso también terminarán, así que evite la función principal prematura Finalizar.

    • Llamar a la función exit () en cualquier subproceso hará que el proceso finalice. Una vez que finalice el proceso, todos los subprocesos del proceso finalizarán.

    La siguiente es una descripción de las API relacionadas con la salida de los subprocesos pthread_exit/pthread_cancel y pthread_join/pthread_tryjoin_np.

    /* 
    El hilo sale activamente pthread_exit 
        #include <pthread.h> 
        void pthread_exit(void *retval); 
        La función pthread_exit es la función de salida del hilo. Al salir, puede pasar un tipo de datos void* al hilo principal. Si Elija no pasar los datos, el parámetro se puede completar con NULL. 
        El único parámetro value_ptr de la función pthread_exit es el valor de retorno de la función. Siempre que el segundo parámetro value_ptr en pthread_join no sea NULL, este valor se pasará a value_ptr. El hilo sale pasivamente de pthread_cancel y otros hilos usan esta función 
    para
    dejar que otro hilo salga 
        #include < pthread.h> 
        int pthread_cancel(pthread_t hilo); 
        Éxito: Devuelve 0. 
        Esta función pasa un número tid y forzará la salida del hilo señalado por el tid. Si se ejecuta correctamente, se obtendrá 0 regresó. 
    ​Reciclaje de recursos 
    de subproceso 
        #include <pthread.h> 
        Esta función es una función de reciclaje de subprocesos. El estado predeterminado está bloqueado y no regresará hasta el hilo se recicla con éxito. El primer parámetro es el número tid del subproceso que se reciclará, y el segundo parámetro son los datos recibidos del subproceso después de que el subproceso se recicla, o el subproceso se cancela y devuelve PTHREAD_CANCELED. 
    Reciclaje de recursos de subprocesos (sin bloqueo, se requiere consulta de bucle) 
        int pthread_join(pthread_t thread, void **retval);
        
        #define _GNU_SOURCE             
        #include <pthread.h> 
        int pthread_tryjoin_np(pthread_t thread, void **retval); 
        Esta función es una función de reciclaje en modo sin bloqueo. Determina si se recicla el hilo a través del valor de retorno. Si el reciclaje es exitoso , devuelve 0. Los parámetros restantes son los mismos que pthread_join consistentes. 
    La diferencia de uso entre el modo 
    de bloqueo 
        Reciclar subprocesos en modo de bloqueo a través de la función pthread_join casi estipula el orden de reciclaje de subprocesos. Si el primer subproceso que se recicla no sale, siempre se bloqueará, lo que resultará en las salidas posteriores primero, el hilo no se puede reciclar a tiempo. 
        Al utilizar el hilo de reciclaje sin bloqueo a través de la función pthread_tryjoin_np, los recursos se pueden reciclar libremente de acuerdo con la secuencia de salida. 
    */
  • Propiedades del hilo relacionadas:

    Consulte el blog del controlador alto del atributo de hilo pthread_attr_init - blog de CSDN pthread_attr_destroy , la introducción detallada del atributo de hilo al atributo de hilo pthread_attr_t - columna de Robin Hu - blog de CSDN .

    /* Defina la variable de atributo de subproceso pthread_attr_t, utilizada para establecer atributos de subproceso, que incluye principalmente el atributo de alcance (utilizado para distinguir el modo de usuario o el modo de kernel), atributo de separación (desmontable/unible), dirección de pila, tamaño de pila, prioridad*/ pthread_attr_t attr_1, 
    attr_2_3_4 [3]; 
    ​/
    * Primero llame a pthread_attr_init para inicializar de forma predeterminada la variable de atributo del hilo, y luego llame a la función de clase pthread_attr_xxx para cambiar su valor*/ 
    pthread_attr_init(&attr_1); 
    ​/
    * Por ejemplo (aquí hay un ejemplo, hay muchas API para configurar atributos) 
        pthread_attr_setdetachstate(&attr_1,PTHREAD_CREATE_DETACHED); para establecer el atributo separable del hilo (la función pthread_detach también establece este atributo de un determinado hilo). 
        De forma predeterminada, el hilo se puede unir o no se puede separar. En este caso Después de que el hilo principal espera a que salga el hilo secundario, solo cuando regresa la función pthread_join () el hilo creado finaliza y se pueden liberar los recursos del sistema que ocupa. Si el subproceso está configurado en estado no unible o desconectado, es decir, recupera activamente recursos después de que sale el subproceso secundario, el subproceso principal no necesita llamar a pthread_join para esperar a que salga el subproceso secundario.
        Puede usar la función pthread_attr_setdetachstate para establecer el atributo del hilo detachstate en uno de los dos valores legales siguientes: configurarlo en PTHREAD_CREATE_DETACHED para iniciar el hilo en un estado separado (no unible); o configurarlo en PTHREAD_CREATE_JOINABLE para iniciar el hilo normalmente (unible, el valor predeterminado). ). 
    Puede utilizar la función pthread_attr_getdetachstate para obtener el atributo del hilo datachstate actual. 
        Además, generalmente no se recomienda cambiar activamente la prioridad de un hilo.
        El enlace de referencia relacionado con los atributos del subproceso anterior presenta más API de configuración de atributos, incluida la herencia, la estrategia de programación (dos opciones + otros métodos) y los parámetros de programación 
    */ 
    ​/
    * Aquí, los atributos del subproceso se configuran en subprocesos a nivel de sistema o en subprocesos de usuario. subprocesos de nivel*/ 
    pthread_attr_setscope(&attr_1, PTHREAD_SCOPE_SYSTEM); /* subprocesos a nivel de sistema, adecuados para informática intensiva*/ 
    pthread_attr_setscope(&attr_2_3_4[0], PTHREAD_SCOPE_PROCESS); /* subprocesos a nivel de usuario, adecuado para IO intensivo* 
    / 
    * Luego use este atributo para crear un hilo*/ 
     pthread_create(&tid, &attr_1, fn, arg); 
    ​/
    * Puede establecer todos los atributos en valores NULL, reiniciarlos y luego configurarlos*/ 
    pthread_attr_destroy(&attr_1);

    Competencia de subprocesos: implementación de subprocesos de Linux de referencia y LinuxThread frente a NPTL y subprocesos y subprocesos a nivel de kernel a nivel de usuario y procesamiento de señales - blcblc - Blog Park (cnblogs.com) , (227 mensajes) Blog de análisis de procesos de Linux_deep_explore-Blog-CSDN .

    Los subprocesos a nivel del sistema competirán con otros procesos por intervalos de tiempo, y los usuarios y los subprocesos solo competirán con otros usuarios y subprocesos dentro del proceso por la programación.

    Los pthreads posteriores a Linux 2.6 se implementan usando NPTL (mejor soporte para POSIX). Todos son subprocesos 1:1 a nivel de sistema (un subproceso equivale a un proceso y 1:n equivale a n subprocesos que compiten entre sí en un proceso). ) modelo Es un hilo a nivel de sistema. Al llamar a clone () en pthread_create (), se configura CLONE_VM, por lo que desde la perspectiva del kernel, se generan dos procesos con el mismo espacio de memoria. Por lo tanto, cuando el estado del usuario crea un nuevo hilo, el estado del núcleo genera un nuevo proceso.

    Por tanto, Linux es un sistema operativo multitarea y multiproceso. Pero el mecanismo de subprocesos que implementa es muy singular: desde la perspectiva del núcleo, no tiene el concepto de subprocesos. Linux implementa todos los subprocesos como procesos, y un subproceso se considera simplemente un proceso que comparte ciertos recursos con otros procesos. Los procesos y subprocesos tienen su propia task_struct, y no hay diferencia entre los dos a los ojos del kernel.

  • etc.

Rutinas que utilizan estas API

/* Archivo: Ejemplo de API básica de subprocesos\Reciclaje pasivo de rutina de API de subprocesos en Linux.c */ 
#define
_GNU_SOURCE 
#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd .h> 
#include <errno.h> 
​/
* Ejemplo: 
crear el subproceso 1 a nivel de sistema, sin parámetros entrantes y salientes, bucle infinito, salir en condiciones específicas, usar pthread_join para reciclar y 
crear subprocesos 2, 3 a nivel de usuario, 4 tres subprocesos, el número de subproceso usa una matriz, los parámetros entrantes y salientes se pasan y pthread_tryjoin_np se usa 
para reciclar. Compile bajo el compilador gnu 
en 
*/ 
​/
* Se utiliza para establecer atributos de hilo, que incluyen principalmente el atributo de alcance (usado para distinguir el modo de usuario o el modo kernel), atributo de separación (separable/unible), dirección de pila, tamaño de pila, prioridad*/ pthread_attr_t attr_1, attr_2_3_4[3]; 
/ 
* Un puntero a un identificador de subproceso, que distingue los subprocesos, se denomina número de subproceso y solo es válido en este proceso. La esencia es unsigned long int */ 
pthread_t id_1, id_2_3_4[3] 
;
/* Hilo 1, sin parámetros entrantes y salientes, salir después de la ejecución, usar pthread_join para reciclar*/ 
void *thread_1(void *in_arg) 
{ 
    int i = 0; 
    printf("thread_1 ID = %lu\n", ( unsigned long )pthread_self()); 
    for(;;) 
    { 
        printf("thread_1 tiempos de impresión = %d\n", ++i); 
        if(i >= 3) 
            pthread_exit(NULL); 
            /* Usar pthread_exit() para llamar el valor de retorno del subproceso, se utiliza para salir del subproceso, pero los recursos ocupados por el subproceso saliente no se liberarán con la terminación del subproceso*/ sleep(1); /* 
        sleep() unidad de segundos, el programa se cuelga durante 1 segundo* / 
    } 
} 
​/
* Thread 2 3 4 */ 
void *thread_2_3_4(void *in_arg) 
{ 
    /* Debe modificarse estáticamente; de ​​lo contrario, pthread_join/pthread_tryjoin_np no puede obtener el valor correcto*/ 
    static char* exit_arg;
    "id_4 jajaja"); 
    }demás
     
    {
        pthread_exit(NULL); 
    } 
​pthread_exit
    ((void*)in_arg); 
} 
​int
main(void) 
{ 
    int ret = -1, i = 0, return_thread_num = 0; 
    char *str_gru[3]; 
    void *exit_arg = NULL; 
​pthread_attr_init
    (&attr_1); 
    pthread_attr_setscope(&attr_1, PTHREAD_SCOPE_SYSTEM); /* 系统级线程 */ 
    
    for(i = 0;i < 3;i++) 
    { 
        pthread_attr_init(&attr_2_3_4[i]); 
        pthread_attr_setscope(&attr_2_3_4[i], PTHREAD_SCOPE_PROCESS); /* 用户级线程 */ 
    } 
​/
    * 创建线程 1 */ 
    ret = pthread_create(&id_1, &attr_1, thread_1, NULL); 
    si(ret!= 0)
    { 
        /* perror genera un mensaje de error descriptivo con el error estándar stderr. Cuando se produce un error al llamar a "algunas" funciones, la función ha restablecido el valor de errno. La función perror simplemente genera parte de la información ingresada y el error correspondiente a errno*/ 
        perror("pthread1, pthread_create: "); 
        return -1; 
    } 
    
    /* Crea subprocesos 2, 3, 4 */ 
    for(i = 0 ;i < 3;i++) 
    { 
        str_gru[i] = (char*)malloc(sizeof(char) * 42 + i); 
        ret = pthread_create(&id_2_3_4[i], &attr_2_3_4[i], thread_2_3_4, (void *)str_gru [i]); 
        if(ret != 0) 
        { 
            perror("pthread 2 3 4, pthread_create: "); 
            return -1; 
        } } 
    / 
    
    * Espere a que finalicen todos los subprocesos, primero espere los subprocesos 2, 3 y 4 uno tras otro. Salga sin requisito de orden, espere a que salga el hilo 1*/ 
    for(;;) 
    {
        for(i = 0;i < 3;i++)
        { 
            /* El np de pthread_tryjoin_np no es portátil y es una API estándar no POSIX especificada por gnu. Solo puede ser utilizada por compiladores en Linux*/ if( pthread_tryjoin_np(id_2_3_4[i], &exit_arg) == 0 
            ) 
            { 
                printf ("pthread: %lu sale con str: %s\n", (unsigned long)id_2_3_4[i], (char*)exit_arg); free( str_gru[i]); return_thread_num++; } } if 
                ( 
                return_thread_num 
            > 
        = 
        3 ) romper; 
    } 
    pthread_join(id_1, NULL); 
​devuelve
    0; 
}

Supongo que te gusta

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