Programación del sistema Linux (6): subprocesos

Referencias

1. Concepto de hilo

1.1 ¿Qué es un hilo?

  • hilo
    • LWP: proceso liviano, proceso liviano, la esencia sigue siendo un proceso (en un entorno Linux)
    • Hay una PCB independiente, pero no hay espacio de direcciones independiente (compartido)
    • unidad de ejecución más pequeña
  • proceso
    • Espacio de direcciones independiente, PCB propio
    • La unidad más pequeña de asignación de recursos puede considerarse como un proceso con un solo subproceso.
  • La diferencia entre proceso e hilo.
    • Depende de si se comparte el espacio de direcciones. Vivir solo (proceso); compartir (hilo). Bajo Linux:

1.2 Principio de implementación del hilo del kernel de Linux

  • En los sistemas tipo Unix, no existía el concepto de "subprocesos" al principio, solo se introdujo en la década de 1980 y el concepto de subproceso se implementó con la ayuda del mecanismo de proceso. Por lo tanto, en este tipo de sistemas, los procesos y los hilos están estrechamente relacionados.

    • Los procesos livianos también tienen PCB. Las funciones subyacentes utilizadas para crear subprocesos son las mismas que las de los procesos, que son todos clones.
    • Desde la perspectiva del kernel, los procesos y subprocesos son los mismos y tienen diferentes PCB, pero las tablas de páginas de tres niveles que apuntan a los recursos de memoria en la PCB son las mismas.
    • Los procesos pueden transformarse en hilos.
    • Un hilo puede verse como una colección de registros y pilas.
    • En Linux, los subprocesos son la unidad más pequeña de ejecución y los procesos son la unidad más pequeña de recursos asignados.
  • Ver número de LWP

    $ ps -Lf pid
    
  • Mapeo de nivel 3

    • PCB de proceso --> Directorio de páginas (se puede ver como una matriz, la primera dirección se encuentra en la PCB) -> Tabla de páginas --> Página física --> Unidad de memoria
    • Para procesos , la misma dirección (la misma dirección virtual) se puede usar repetidamente en diferentes procesos sin conflicto. La razón es que, aunque sus direcciones virtuales son las mismas, sus directorios de páginas, tablas de páginas y páginas físicas son diferentes. La misma dirección virtual se asigna a diferentes unidades de memoria de páginas físicas y, en última instancia, accede a diferentes páginas físicas.
    • Los subprocesos son diferentes . Los dos subprocesos tienen PCB independientes, pero comparten el mismo directorio de páginas, lo que significa que comparten la misma tabla de páginas y página física , por lo que los dos PCB comparten un espacio de direcciones.
  • De hecho, ya sea fork para crear un proceso o pthread_create para crear un hilo, la implementación subyacente llama a la misma función del kernel clonar.

    • Si copia el espacio de direcciones de la otra parte, se generará un proceso .
    • Si se comparte el espacio de direcciones de la otra parte, se genera un hilo .
  • El kernel de Linux no distingue entre procesos y subprocesos, sólo a nivel de usuario. Todas las funciones de operación de subprocesos pthread_* son funciones de biblioteca

Insertar descripción de la imagen aquí

1.3 Hilo de recursos compartidos/no compartidos

  • Hilo de recursos compartidos
    • tabla de descriptores de archivos
    • Cómo se procesa cada señal
    • directorio de trabajo actual
    • ID de usuario e ID de grupo
    • Espacio de direcciones de memoria (.text/.data/.bss/heap/shared Library)
  • Hilo de recursos no compartidos
    • ID del hilo
    • Contexto del procesador y punteros de pila (pila del kernel)
    • Espacio de pila independiente (pila de espacio de usuario)
    • error variable
    • palabra de máscara de señal
    • Prioridad de programación

1.4 Ventajas y desventajas de los hilos

  • ventaja
    • Mejorar la simultaneidad del programa
    • Baja sobrecarga de recursos
    • La comunicación y el intercambio de datos son convenientes
  • defecto
    • Función de biblioteca, inestable
    • Depuración, dificultad para escribir, no compatible con gdb
    • Pobre soporte de señal

Las ventajas son relativamente sobresalientes y las deficiencias no son defectos. En Linux, debido al método de implementación, la diferencia entre procesos y subprocesos no es muy grande: los subprocesos se utilizan primero.

2. Primitivas de control de subprocesos

2.1 función pthread_self

  • Obtenga el ID del hilo , su función corresponde a la función getpid() en el proceso
    #include <pthread.h>
    
    // 返回值 成功 0; 失败:无
    pthread_t pthread_self(void);
    // Compile and link with -pthread
    
  • ID del hilo: tipo pthread_t
    • Esencia: es un entero sin signo (% lu) en Linux y puede ser una estructura en otros sistemas.
    • El ID del hilo es la marca de identificación interna del proceso (se permite que el ID del hilo sea el mismo entre dos procesos)

La variable global pthread_t tid no debe usarse para pasar parámetros a través de pthread_create en el subproceso secundario para obtener el ID del subproceso. En su lugar, se debe usar pthread_self

2.2 función pthread_create

  • Cree un nuevo hilo , cuya función corresponda a la función fork() en el proceso

    #include <pthread.h>
    
    // 返回值 成功 0; 失败:对应的错误号
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
    // Compile and link with -pthread
    
    • Parámetro 1: parámetro saliente, guarde el ID del hilo asignado por el sistema
    • Parámetro 2: normalmente se pasa NULL , lo que significa utilizar los atributos predeterminados del hilo. Si desea utilizar atributos específicos, también puede modificar este parámetro.
    • Parámetro 3: Puntero de función, que apunta a la función principal del hilo (cuerpo del hilo), cuando finaliza la función, el hilo finaliza.
    • Parámetro 4: Parámetros utilizados durante la ejecución de la función principal del hilo
  • Después de llamar a pthread_create() en un hilo para crear un nuevo hilo , el hilo actual regresa de pthread_create() y continúa la ejecución, y el código ejecutado por el nuevo hilo está determinado por el puntero de función start_routine pasado a pthread_create.

    • La función start_routine recibe un parámetro, que se le pasa a través del parámetro arg de pthread_create. El tipo de parámetro es void *. El tipo de interpretación de este puntero lo define la persona que llama.
    • El tipo de valor de retorno de start_routine también es void *, y la persona que llama también define el significado de este puntero.
    • Cuando start_routine regresa, el hilo sale. Otros hilos pueden llamar a pthread_join para obtener el valor de retorno de start_routine, similar al proceso padre que llama a wait(2) para obtener el estado de salida del proceso hijo.
  • Después de que pthread_create regresa con éxito, la ID del subproceso recién creado se completa en la unidad de memoria a la que apunta el parámetro del subproceso.

    • El tipo de ID de proceso es pid_t. El ID de cada proceso es único en todo el sistema. Puede obtener el ID del proceso actual llamando a getpid(2), que es un valor entero positivo.
    • El tipo de ID de subproceso es pthread_t, que solo se garantiza que es único en el proceso actual. En diferentes sistemas, el tipo pthread_t tiene diferentes implementaciones. Puede ser un valor entero, una estructura o una dirección, por lo que no puede ser simplemente considerado como Utilice printf para imprimir números enteros y llame a pthread_self(3) para obtener el ID del hilo actual.
Caso 1
  • Cree un nuevo hilo e imprima el ID del hilo.
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <pthread.h>
    
    void sys_err(const char *str) {
          
          
        perror(str);
        exit(1);
    }
    
    // 子线程
    void *tfn(void *arg) {
          
          
        printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
    
        return NULL;
    }
    
    // 主线程
    int main(int argc, char *argv[]) {
          
          
        pthread_t tid;
    
        // attr 取 NULL 表示取默认值
        int ret = pthread_create(&tid, NULL, tfn, NULL);
        if (ret != 0) {
          
          
            perror("pthread_create error");
        }
        printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());
    
        pthread_exit((void *)0);  // 等价下面两行代码,此方法更优
    
        //sleep(1);
    	//return 0;
    }
    
    # pthread 不是 Linux 下的默认的库,链接的时候无法找到 phread 库中函数的入口地址,于是链接会失败
    # 所以在 gcc 编译的时候,要加 -pthread 参数即可解决
    $ gcc pthread_create.c -o pthread_create -pthread
    $ ./pthread_create
    main: pid = 2986, tid = 140380929427264
    thread: pid = 2986, tid = 140380921087744
    
Caso 2
  • Cree varios hilos en un bucle y cada hilo imprime qué hilo se crea.
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <pthread.h>
    
    void sys_err(const char *str) {
          
          
    	perror(str);
    	exit(1);
    }
    
    void *tfn(void *arg) {
          
          
        // 使用 int 报错:warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
        long i = (long)arg;  // 强制类型转换
        sleep(i);
        printf("I'm %ldth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
    
        return NULL;
    }
    
    int main(int argc, char *argv[]) {
          
          
        long i;
        int ret;
        pthread_t tid;
    
        for (i = 0; i < 5; i++) {
          
          
            ret = pthread_create(&tid, NULL, tfn, (void *)i);  // i 传参采用值传递,借助强制类型转换
            if (ret != 0) {
          
          
                sys_err("pthread_create error");
            }
        }
    
        sleep(i);
        printf("I'm main, pid = %d, tid= %lu\n", getpid(), pthread_self());
    
    	return 0;
    }
    
    $ gcc pthread_more.c -o pthread_more -pthread
    $ ./pthread_more 
    I'm 1th thread: pid = 3163, tid = 139852150068992
    I'm 2th thread: pid = 3163, tid = 139852141676288
    I'm 3th thread: pid = 3163, tid = 139852133283584
    I'm 4th thread: pid = 3163, tid = 139851990673152
    I'm 5th thread: pid = 3163, tid = 139852054001408
    I'm main: pid = 3163, tid = 139852158408512
    

2.3 Hilos y uso compartido

  • Compartir variables globales entre hilos
    • Los subprocesos comparten espacios de direcciones, como segmentos de datos y segmentos de código de forma predeterminada, y las variables globales se utilizan comúnmente. El proceso no comparte variables globales y solo puede usar mmap
Caso
  • Verificar que los datos globales se compartan entre subprocesos
    #include <stdio.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int var = 100;
    
    void *tfn(void *arg) {
          
          
        var = 200;
        printf("thread, var = %d\n", var);
        
        return NULL;
    }
    
    int main(void) {
          
          
        printf("At first var = %d\n", var);
        
        pthread_t tid;
        pthread_create(&tid, NULL, tfn, NULL);
        sleep(1);
        
        printf("after pthread_create, var = %d\n", var);
        
        return 0;
    }
    
    $ gcc ttt.c -o ttt -pthread
    $ ./ttt 
    At first var = 100
    thread, var = 200
    after pthread_create, var = 200
    

2.4 función pthread_exit

  • Salir de un solo hilo
    #include <pthread.h>
    
    void pthread_exit(void *retval);
    // Compile and link with -pthread
    
    • El parámetro retval representa el estado de salida del hilo, generalmente se pasa NULL
  • Está prohibido utilizar la función exit () en subprocesos, lo que provocará la salida de todos los subprocesos del proceso.
    • En su lugar, utilice la función pthread_exit para salir de un solo hilo.
    • Salir en cualquier hilo hace que el proceso salga. Otros hilos no han completado su trabajo. Cuando el hilo de control principal sale, no puede regresar ni salir.

La unidad de memoria a la que apunta el puntero devuelto por pthread_exit o return debe ser global o asignarse mediante malloc. No se puede asignar en la pila de la función del subproceso, porque la función del subproceso ya salió cuando otros subprocesos obtienen el puntero de retorno.

Caso
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

void sys_err(const char *str) {
    
    
	perror(str);
	exit(1);
}

void func(void) {
    
    
    pthread_exit(NULL);         // 将当前线程退出

    return ;
}

void *tfn(void *arg) {
    
    
    long i = (long)arg;         // 强制类型转换
    sleep(i);

    if (i == 2) {
    
    
        //exit(0);              // 表示退出进程
        //return NULL;          // 表示返回到调用者那里去
        //func();
        pthread_exit(NULL);     // 将当前线程退出
    }
    printf("I'm %ldth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());

    return NULL;
}

int main(int argc, char *argv[]) {
    
    
    long i;
    int ret;
    pthread_t tid;

    for (i = 0; i < 5; i++) {
    
    
        ret = pthread_create(&tid, NULL, tfn, (void *)i);  // i 传参采用值传递,借助强制类型转换
        if (ret != 0) {
    
    
            sys_err("pthread_create error");
        }
    }

    sleep(i);
    printf("I'm main, pid = %d, tid= %lu\n", getpid(), pthread_self());

    return 0;
}
$ gcc pthread_exit.c -o pthread_exit -pthread
$ ./pthread_exit
I'm 1th thread: pid = 3389, tid = 140125255145216
I'm 2th thread: pid = 3389, tid = 140125246752512
I'm 4th thread: pid = 3389, tid = 140125238359808
I'm 5th thread: pid = 3389, tid = 140125229967104
I'm main: pid = 3389, tid = 140125263484736

en conclusión

  • salir: salir del proceso
  • return: regresar a la persona que llama
  • pthread exit(): sale del hilo que llama a esta función

2.5 función pthread_join

  • Bloquea la espera de que salga el hilo (el hilo principal espera la terminación del hilo secundario) y obtiene el estado de salida del hilo.Su función corresponde a la función waitpid () en el proceso.

    #include <pthread.h>
    
    // 返回值 成功:0  失败:错误号
    int pthread_join(pthread_t thread, void** retval);
    // Compile and link with -pthread.
    
    • hilo: ID del hilo (no un puntero)
    • retval: almacena el estado final del hilo
      • En el proceso : valor de retorno principal, parámetro de salida –>int; esperar a que finalice el proceso hijo esperar parámetro de función –>int*
      • En el hilo : Valor de retorno de la función principal del hilo, pthread_exit–>void*; Espere a que finalice el hilo Parámetros de la función pthread_join-->void**
  • El hilo que llama a esta función se colgará y esperará hasta que finalice el hilo con id. Los subprocesos se terminan de diferentes maneras y el estado de terminación obtenido a través de pthread_join es diferente, que se resume a continuación

    • Si el hilo regresa mediante retorno, la unidad a la que apunta retval almacena el valor de retorno de la función del hilo.
    • Si el subproceso finaliza anormalmente llamando a pthread_cancel desde otro subproceso, la constante PTHREAD_CANCELED se almacena en la unidad señalada por retval.
    • Si el subproceso finaliza llamando al propio pthread_exit, la unidad a la que apunta retval almacena los parámetros pasados ​​a pthread_exit.
    • Si no está interesado en el estado de terminación del hilo, puede pasar NULL al parámetro retval.
Caso
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

struct thrd {
    
    
    int var;
    char str[256];
};

void sys_err(const char *str) {
    
    
	perror(str);
	exit(1);
}
/*
void *tfn(void *arg) {
    struct thrd *tval;

    tval = malloc(sizeof(tval));
    tval->var = 100;
    strcpy(tval->str, "hello thread");

    return (void *)tval;
}
*/
/*
void *tfn(void *arg) {
    // 此处 tval 为局部变量,随函数调用产生而产生,函数调用结束后栈空间就没了,对应的 tval 也没了
    struct thrd tval;              

    tval.var = 100;
    strcpy(tval.str, "hello thread");

    // 局部变量 tval 地址不可做返回值
    return (void *)&tval;
}
*/ 
void *tfn(void *arg) {
    
    
    struct thrd *tval = (struct thrd *)arg;

    tval->var = 100;
    strcpy(tval->str, "hello thread");

    return (void *)tval;
}

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

    struct thrd arg;
    struct thrd *retval;

    int ret = pthread_create(&tid, NULL, tfn, (void *)&arg);
    if (ret != 0)
        sys_err("pthread_create error");

    // tid 为传入参数,retval 为传出参数
    // 等待线程的结束,并将其返回值赋给 retval
    ret = pthread_join(tid, (void **)&retval);
    if (ret != 0)
        sys_err("pthread_join error");

    printf("child thread exit with var= %d, str= %s\n", retval->var, retval->str);
    
    pthread_exit(NULL);
}

2.5 función pthread_detach

  • Implementar la separación de hilos

    #include <pthread.h>
    
    int pthread_detach(pthread_t thread);
    // Compile and link with -pthread
    
  • estado de desprendimiento del hilo

    • Especifique este estado y el subproceso se desconectará activamente del subproceso de control principal. Una vez finalizado un subproceso, otros subprocesos no obtienen su estado de salida, sino que lo liberan directa y automáticamente, lo que se usa comúnmente en redes y servidores multiproceso.
  • Si el proceso tiene un mecanismo similar a la separación de subprocesos, no se generarán procesos zombies.

    • Causas de los procesos zombies: después de que el proceso muere, la mayoría de los recursos se liberan, pero aún existen recursos residuales en el sistema, lo que hace que el kernel piense que el proceso aún existe.
  • Generalmente, después de que finaliza un subproceso, su estado de terminación se conserva hasta que otros subprocesos llamen a pthread_join para obtener su estado. Pero el subproceso también se puede configurar en el estado de desconexión. Una vez que dicho subproceso finaliza, inmediatamente recuperará todos los recursos que ocupa sin conservar el estado de terminación.

  • No se puede llamar a pthread_join en un subproceso que ya está en estado desconectado; dicha llamada devolverá un error EINVAL. En otras palabras, si se ha llamado a pthread_detach en un hilo, no se puede volver a llamar a pthread_join.

Caso
  • Utilice la función pthread_detach para implementar la separación de hilos
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <pthread.h>
    
    void *tfn(void *arg) {
          
          
        printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
    
        return NULL;
    }
    
    int main(int argc, char *argv[]) {
          
          
        pthread_t tid;
    
        int ret = pthread_create(&tid, NULL, tfn, NULL);
        if (ret != 0) {
          
          
            fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
            exit(1);
        }
        ret = pthread_detach(tid);              // 设置线程分离` 线程终止,会自动清理pcb,无需回收
        if (ret != 0) {
          
          
            fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
            exit(1);
        }
    
        sleep(1);
    
        ret = pthread_join(tid, NULL);
        if (ret != 0) {
          
          
            fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
            exit(1);
        }
    
        printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());
    
        pthread_exit((void *)0);
    }
    
    $ gcc pthread_detach.c -o pthread_detach -pthread
    $ ./pthread_detach 
    thread: pid = 3684, tid = 139762658100992
    pthread_join error : Invalid argument
    

2.6 función pthread_cancel

  • Mata (cancela) el hilo , su función corresponde a la función kill() en el proceso
    #include <pthread.h>
    
    int pthread_cancel(pthread_t thread);
    // Compile and link with -pthread.
    
  • La cancelación del hilo no es en tiempo real, pero hay un cierto retraso y es necesario esperar a que el hilo alcance un determinado punto de cancelación (punto de control).
    • De manera similar a jugar un juego guardado, debes llegar a un lugar designado (punto de guardado, como una posada, un almacén, una ciudad, etc.) para guardar el progreso. La eliminación del hilo no se puede completar de inmediato, debe llegar al punto de cancelación.
    • Punto de cancelación: es una posición donde el hilo verifica si ha sido cancelado y toma las medidas solicitadas.
    • Se puede considerar aproximadamente que una llamada al sistema (ingresando al kernel) es un punto de cancelación. Si no hay un punto de cancelación en el hilo, puede establecer un punto de cancelación usted mismo llamando a la función pthread_testcancel.
Caso
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

void *tfn1(void *arg) {
    
    
    printf("thread 1 returning\n");

    return (void *)111;    
}

void *tfn2(void *arg) {
    
    
    printf("thread 2 exiting\n");
    pthread_exit((void *)222);
}

void *tfn3(void *arg) {
    
    
    while (1) {
    
    
        pthread_testcancel();  // 自己添加取消点
    }   

    return (void *)666;
}

int main(void) {
    
    
    pthread_t tid;
    void *tret = NULL;

    pthread_create(&tid, NULL, tfn1, NULL);
    pthread_join(tid, &tret);
    printf("thread 1 exit code = %ld\n\n", (long)tret);

    pthread_create(&tid, NULL, tfn2, NULL);
    pthread_join(tid, &tret);
    printf("thread 2 exit code = %ld\n\n", (long)tret);

    pthread_create(&tid, NULL, tfn3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &tret);
    printf("thread 3 exit code = %ld\n", (long)tret);

    return 0;
}
$ gcc pthread_cancel.c -o pthread_cancel -pthread
$ ./pthread_cancel
thread 1 returning
thread 1 exit code = 111

thread 2 exiting
thread 2 exit code = 222

thread 3 exit code = -1
Terminar el modo de hilo
  • Hay tres formas de terminar un hilo sin terminar todo el proceso
    • Regreso de la función principal del hilo. Este método no es aplicable al hilo de control principal. El retorno de la función principal equivale a llamar a la salida.
    • Un hilo puede llamar a pthread_cancel para terminar otro hilo en el mismo proceso
    • Un hilo puede terminarse llamando a pthread_exit

3. Atributos del hilo

  • Todos los subprocesos discutidos anteriormente utilizan las propiedades predeterminadas de los subprocesos, y las propiedades predeterminadas ya pueden resolver la mayoría de los problemas encontrados durante el desarrollo. Si tiene requisitos más altos para el rendimiento del programa, debe configurar los atributos del subproceso.

    • Por ejemplo, puede reducir el uso de memoria configurando el tamaño de la pila de subprocesos.
    • Aumentar el número máximo de hilos.
  • El valor del atributo no se puede establecer directamente y se deben usar funciones relacionadas para la operación. La función de inicialización es pthread_attr_init. Esta función debe llamarse antes de la función pthread_create y luego se debe usar la función pthread_attr_destroy para liberar recursos.

3.1 Inicialización de atributos de hilo

  • Los atributos del hilo deben inicializarse primero y luego pthread_create crea el hilo.
    #include <pthread.h>
    
    // 返回值 成功返回 0,失败返回对应的错误号
    int pthread_attr_init(pthread_attr_t *attr);     // 初始化线程属性
    int pthread_attr_destroy(pthread_attr_t *attr);  // 销毁线程属性所占用的资源
    // Compile and link with -pthread.
    

3.2 Estado de separación de hilos

  • El estado de separación de un hilo determina cómo un hilo termina a sí mismo.
    • Estado no separado : el atributo predeterminado de un hilo es el estado no separado. En este caso, el hilo original espera a que finalice el hilo creado. Solo cuando regresa la función pthread _join(), el hilo creado finaliza y se pueden liberar los recursos del sistema que ocupa.
    • Estado desconectado : el subproceso desconectado no está esperando a otros subprocesos. Cuando termina de ejecutarse, el subproceso finaliza y los recursos del sistema se liberan inmediatamente. Debes elegir el estado de separación apropiado según tus propias necesidades.
    #include <pthread.h>
    
    // 返回值 成功返回 0,失败返回对应的错误号
    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);        // 设置线程属性
    int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); // 获取线程属性
    // Compile and link with -pthread.
    
    • atributo
      • Propiedades del hilo inicializado
    • separar estado
      • PTHREAD_CREATE_DETACHED (hilo separado)
      • PTHREAD_CREATE_JOINABLE (hilo no separado)

3.3 Ejemplo de control de atributos de hilo

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

void *tfn(void *arg) {
    
    
    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());

    return NULL;
}

int main(int argc, char *argv[]) {
    
    
    pthread_t tid;
    pthread_attr_t attr;

    int ret = pthread_attr_init(&attr);
    if (ret != 0) {
    
    
        fprintf(stderr, "attr_init error:%s\n", strerror(ret));
        exit(1);
    }

    ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);      // 设置线程属性为分离属性
    if (ret != 0) {
    
    
        fprintf(stderr, "attr_setdetachstate error:%s\n", strerror(ret));
        exit(1);
    }

    ret = pthread_create(&tid, &attr, tfn, NULL);
    if (ret != 0) {
    
    
        perror("pthread_create error");
    }

    ret = pthread_attr_destroy(&attr);
    if (ret != 0) {
    
    
        fprintf(stderr, "attr_destroy error:%s\n", strerror(ret));
        exit(1);
    }

    ret = pthread_join(tid, NULL);
    if (ret != 0) {
    
    
        fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
        exit(1);
    }

    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());

    pthread_exit((void *)0);
}
$ gcc pthread_attr.c -o pthread_attr -pthread
$ ./pthread_attr 
pthread_join error:Invalid argument

3.4 Cosas a tener en cuenta al usar hilos

  • El hilo principal sale y otros hilos no salen. El hilo principal debe llamar a pthread_exit
  • Evite los hilos de zombies
    • unión de subprocesos
    • desprendimiento del hilo
    • pthread create especifica atributos separados
    • El subproceso que se está uniendo puede liberar todos sus recursos de memoria antes de que regrese la función de unión, por lo que no se debe devolver el valor en la pila del subproceso reciclado.
  • La memoria solicitada por malloc y mmap puede ser liberada por otros subprocesos
  • Debe evitar llamar a fork en el modelo de subprocesos múltiples a menos que exec se ejecute inmediatamente. Solo el subproceso que llama a fork existe en el proceso hijo y otros subprocesos son pthread_exit en el proceso hijo.
  • La semántica compleja de las señales es difícil de coexistir con subprocesos múltiples y debe evitarse la introducción de mecanismos de señales en subprocesos múltiples.

Supongo que te gusta

Origin blog.csdn.net/qq_42994487/article/details/133350868
Recomendado
Clasificación