Cómo terminar correctamente un hilo secundario en ejecución
Primero, echemos un vistazo a todos los métodos para detener el hilo secundario que se está ejecutando actualmente
1 Cualquier hilo llama a exit
2.pthread_exit p>
3.pthread_kill
4.pthread_cancel
Analicemos uno por uno los diversos métodos para terminar un programa en ejecución
Cualquier llamada de hilo sale
Siempre que salga cualquier llamada de hilo, el proceso finalizará. Por supuesto, varios subprocesos también pueden terminar bien, pero este tipo de salida causará el problema de liberación de recursos. proceso Al finalizar, el kernel llama a cerrar todos los descriptores de archivos del proceso que no se han cerrado, por lo que incluso si el programa de usuario no llama a cerrar, el kernel cerrará automáticamente todos los archivos que abrió al finalizar. Sí, el flujo de E/S estándar de C++ también se vaciará y los recursos se liberarán cuando se cierre la salida. Estas cosas no provocarán un desperdicio de recursos (la entrada de la función principal de la llamada al sistema es similar a exit(main(argc,argv))). En la superficie, parece que todos los problemas se pueden manejar bien al final del proceso. De hecho, este no es el caso. La memoria asignada por nuestro programa desde el montón no se puede liberar bien, como el espacio de almacenamiento después de la nueva. y eliminar. El final del proceso de espacio no le ayudará a devolver esta parte de la memoria a la memoria (en el primer borrador de este artículo, debido a la base débil, lo escribí mal aquí. De hecho, no importa cómo. Cuando finaliza el proceso, el sistema liberará todos los recursos solicitados por el código, ya sea en el montón o en la pila (gracias a ZKey por la guía). Es deseable en muchos casos, pero es necesario realizar alguna otra lógica al cerrar. El procesamiento (refiriéndose a la lógica que no es de liberación de recursos) no será muy bueno. Por ejemplo, quiero contar cuánto trabajo se ha completado antes del programa. se elimina. Esta estadística es similar a MapReduce. Debe ser obtenida por cada hilo y finalmente fusionada en un proceso, etc.)
pthread_exit
El escenario de uso. La característica de esta función es que el hilo que se está ejecutando actualmente ejecuta pthread_exit y sale. Cada subproceso puede saber claramente cuándo termina. Es muy fácil de usar en la situación, pero de hecho, muchas veces un hilo no puede saber cuándo terminar, por ejemplo. como cuando se encuentra Ctrl + C o se cierra el proceso. Por supuesto, si se elimina toda interferencia externa, entonces cada hilo puede llamar conscientemente a pthread_exit después de que el hilo haya terminado su propio trabajo. Esto no es lo que este artículo necesita discutir. El escenario de este artículo es discutir cómo lidiar con situaciones especiales.
Hay otro método aquí. Dado que el subproceso secundario puede salir correctamente a través de pthread_exit, podemos procesar la señal cuando encontramos Ctrl + C y finalizar el proceso, y luego entregársela al subproceso que puede ser. accedido por un determinado hilo. Almacene una variable de bandera en el área pública. El hilo verificará la bandera cada vez que se ejecute durante un período de tiempo (muy corto). Si encuentra que necesita terminar, llamará a pthread_exit. Una debilidad de este método es que cuando el subproceso secundario necesita bloquearse, es posible que no tenga tiempo para verificar la bandera durante la operación, como la operación de bloqueo del socket. Si las tareas de su subproceso básicamente no tienen funciones sin bloqueo, entonces esta es una buena solución.
pthread_kill
No te dejes intimidar por este terrible nombre malvado. De hecho, pthread_kill no es tan poderoso como su nombre. Después de usarlo, sentirás que es solo un. uno sin nombre
La responsabilidad de pthread_kill es en realidad solo enviar una señal al hilo especificado. En realidad, no mata un hilo. Por supuesto, es necesario explicar el comportamiento predeterminado de algunas señales. para salir Luego usa pthread_kill para enviar la señal al hilo de destino, y el hilo de destino funcionará de acuerdo con el comportamiento predeterminado de esta señal, que puede ser salir. Por supuesto, también podemos cambiar el comportamiento de obtener una determinada señal para lograr nuestro propósito de terminar el subproceso secundario.
1 #definir _MULTI_THREADED
2 #incluir
3 #incluir
4 # incluir
5 #incluir "check.h"
6
7 #definir NUMTHREADS 3
8 void sighand(int signo);
9
10 void *threadfunc(void *parm)
11 {
12 pthread_t self = pthread_self();
13 pthread_id_np_t tid;
14 int rc;
15
16 pthread_getunique_np(&self, &tid);
17 printf("Hilo 0x%.8x %.8x ingresado\n", tid);
18 errno = 0;
19 rc = dormir( 30);
20 if (rc != 0 && errno == EINTR) {
21 printf("El subproceso 0x%.8x %.8x recibió una señal\ n",
22 tid);
23 return NULL;
24 }
25 printf("Subproceso 0x%.8x %.8x no obtuvo los resultados esperados rc=%d, errno=%d\n",
26 tid, rc, errno);
27 return NULL; p> p>
28 }
29
30 int main(int argc, char **argv)
31 {
32 int rc;
33 int i;
34 acciones de sigaction de estructura;
35 subprocesos pthread_t[NUMTHREADS];
36
37 printf("Ingresar caso de prueba - %s\n", argv[0]);
38
39 printf("Configurar la alarma controlador para el proceso\n");
40 memset(&actions, 0, sizeof(actions));
41 sigemptyset(&actions.sa_mask);
42 acciones.sa_flags = 0;
43 acciones.sa_handler = suspiro y;
p>
44
45 rc = sigaction(SIGALRM,&actions,NULL);
46 checkResults("sigaction\n", rc);
47
48 for(i=0; i 49 rc = pthread_create(&threads[i], NULL, threadfunc, NULL); 50 checkResults("pthread_create()\n", rc); 51 } 52 53 dormir(3); 54 for(i=0; i 55 rc = pthread_kill(threads[i], SIGALRM); 56 checkResults("pthread_kill()\n", rc); 57 } 58 59 for(i=0; i 60 rc = pthread_join(threads[i], NULL); 61 checkResults("pthread_join()\n", rc); 62 } 63 printf("Principal completado\n"); 64 devuelve 0; 65 } 66 67 void sighand(int signo) 68 { 69 pthread_t self = pthread_self(); 70 pthread_id_np_t tid; 71 72 pthread_getunique_np(&self, &tid); 73 printf("Subproceso 0x%.8x %.8x en el controlador de señales\n", p> 74 tid); 75 return; 76 } La salida en ejecución es: 1 Salida: 2 3 Ingrese al caso de prueba - QP0WTEST/TPKILL0 4 Configure el controlador de alarmas para el proceso 5 Se ingresó el hilo 0x00000000 0000000c 6 subproceso 0x00000000 0000000d ingresado 7 subproceso 0x00000000 0000000e ingresado 8 subproceso 0x00000000 0000000c en el controlador de señales 9 subproceso 0x00000000 00000 00c recibió una señal 10 subproceso 0x00000000 0000000d en el controlador de señales p> 11 El subproceso 0x00000000 0000000d recibió una señal 12 El subproceso 0x00000000 0000000e en el controlador de señales 13 El subproceso 0x00000000 0000000e recibió una señal 14 Principal completado Podemos liberar los recursos solicitados por el hilo a través de la señal interceptada, pero desafortunadamente no podemos llamar a pthread_exit en el procesamiento de la señal para terminar el hilo, porque pthread_exit es un intermediario La corriente hilo, y la forma en que se llama la señal puede entenderse como una devolución de llamada del kernel, que no se ejecuta en el mismo hilo, por lo que solo puede manejar la liberación de recursos. El hilo solo puede determinar si ha sido interrumpido (generalmente EINTR). Si desea finalizarlo usted mismo, puede llamar a pthread_exit para salir después del juicio. Este método también es muy factible para operaciones generales, pero en algunos casos no es un método mejor. Por ejemplo, tenemos algunos subprocesos que procesan eventos de E/S de la red. un hilo del servidor que bloquea la lectura de mensajes del Socket. Generalmente procesamos la señal EINTR en la biblioteca IO de la red. Por ejemplo, cuando se usa recv, se encuentra que el valor de retorno es menor que 0. Después de verificar el error, se realizará la operación correspondiente. Es posible que vuelva a recibir, lo que equivale a que mi subproceso no finalice en absoluto, porque es posible que la clase IO de la red no sepa cómo finalizar el subproceso cuando obtiene EINTR. En otras palabras, esta no es una solución portátil particularmente buena. Si las operaciones en su hilo usan muchas clases extrañas y desconocidas y no sabe cómo manejar EINTR, esto es lo que puede tener problemas si lo intenta. para terminarlo. Y si no está particularmente familiarizado con este aspecto, se sentirá muy angustiado: "¿Por qué todos mis códigos de prueba están bien, pero cuando me uno al marco desarrollado por su departamento, ya no están bien? Debe haber algún problema con su estructura." Bueno, para evitar problemas innecesarios, al final no utilicé esta solución. pthread_cancel Esta solución es la que finalmente adopté. Creo que es la mejor solución universal para resolver este problema, aunque es posible que algunos de los problemas de otras soluciones anteriores no se resuelvan. Fácil de resolver, pero en comparación, es bastante bueno pthread_cancel se puede usar solo, porque hay muchos puntos de interrupción en muchas funciones del sistema, y cuando se llaman estas funciones del sistema, sus partes internas se verán afectadas. punto de interrupción para finalizar el hilo, como en el siguiente código, incluso si comentamos el punto de interrupción que establecimos nosotros mismos, el programa pthread_testcancel() aún se cancelará exitosamente porque hay un punto de cancelación dentro de la función printf (si desea saber más Para conocer el punto de cancelación de la función, puede leer la parte del hilo de "Programación del entorno avanzado de Unix") 1 #include 2 #include 3 #include 4 #include 5 void *threadfunc(void *parm) p> 6 { 7 printf("Entró al hilo secundario\n"); 8 while (1) { 9 printf(" El hilo secundario está en bucle\n"); 10 pthread_testcancel(); 11 sleep(1); 12 } 13 return NULL ; 14 } 15 16 int main(int argc, char **argv) 17 { 18 pthread_t thread; 19 int rc=0; 20 21 printf("Entrando al caso de prueba\n"); 22 23 /* Crear un hilo usando los atributos predeterminados */ 24 printf("Crear un hilo usando los atributos NULL\n"); 25 rc = pthread_create(&thread, NULL, threadfunc, NULL); 26 checkResults("pthread_create(NULL)\n", rc); 27 p> 28 /* sleep() no es una forma muy sólida de esperar el hilo */ 29 sleep(1); 30 31 printf("Cancelar el hilo\n"); 32 rc = pthread_cancel(thread); 33 checkResults("pthread_cancel()\n", rc); 34 35 /* sleep() no es una forma muy sólida de esperar el hilo */ 36 dormir(10); 37 printf("Principal completado\n"); 38 devolver 0; 39 } Salida: Ingresando caso de prueba Crear subproceso usando los atributos NULL Subproceso secundario ingresado El subproceso secundario está en bucle Cancelar el hilo Principal completado POSIX garantiza que la mayoría de las funciones de llamada al sistema tengan puntos de cancelación internos. Hemos visto muchas llamadas de cancelación, las funciones de recepción y envío. Finalmente establecerá el punto de cancelación de pthread_testcancel(). De hecho, esto no es tan necesario, entonces, ¿cuándo debería aparecer pthread_testcancel()? "Programación del entorno avanzado de Unix" también dice que cuando se encuentra con una gran cantidad de cálculos básicos (como cálculos científicos), el punto de cancelación debe establecerse usted mismo. Vale, gracias a pthread_cancel, podemos cancelar el hilo fácilmente, pero ¿qué pasa con nuestros recursos? Cuándo liberar... Veamos dos funciones pthread 1.void pthread_cleanup_push(void (*routine)(void *), void *arg 2.void pthread_cleanup_pop(int run); Estas dos funciones pueden garantizar que cualquier forma de hilo finalice después de la llamada a la función 1 y antes de que la llamada a la función 2 llame a la función de devolución de llamada registrada con pthread_cleanup_push Además, también podemos establecer algunos estados a través de la siguiente función.