En la parte II (Creación e identificación de subprocesos) de la serie Linux Thread, discutimos sobre las ID de subprocesos, cómo comparar dos ID de subprocesos y cómo crear un subproceso.
En este artículo nos centraremos principalmente en cómo se termina un subproceso.
Serie de subprocesos de Linux:parte 1, parte 2, parte 3 (este artículo).
Programa de ejemplo de hilo C
Si tomamos el mismo ejemplo que se discutió en la parte II de esta serie:
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; void* doSomeThing(void *arg) { unsigned long i = 0; pthread_t id = pthread_self(); if(pthread_equal(id,tid[0])) { printf("\n First thread processing\n"); } else { printf("\n Second thread processing\n"); } for(i=0; i<(0xFFFFFFFF);i++); return NULL; } int main(void) { int i = 0; int err; while(i < 2) { err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL); if (err != 0) printf("\ncan't create thread :[%s]", strerror(err)); else printf("\n Thread created successfully\n"); i++; } sleep(5); return 0; }
¿Observó que se usaba la función 'dormir ()'? ¿Recibió una pregunta acerca de por qué se está utilizando sleep()? Bueno, si lo hizo, entonces está en el lugar correcto para obtener la respuesta y si no lo hizo, entonces también será una buena lectura.
Si elimino la función sleep() del código anterior y luego intento compilarla y ejecutarla, veo el siguiente resultado:
$ ./threads Thread created successfully First thread processing Thread created successfully
Pero si lo ejecuto con sleep() habilitado, veo el resultado como :
$ ./threads Thread created successfully First thread processing Thread created successfully Second thread processing
Entonces vemos que falta el registro 'Procesamiento del segundo subproceso' en caso de que eliminemos la función dormir().
¿Entonces, por qué pasa ésto? Bueno, esto sucedió porque justo antes de programar el segundo subproceso, el subproceso principal (a partir del cual se crearon los dos subprocesos) completó su ejecución. Esto significa que el subproceso predeterminado en el que se ejecutaba la función main() se completó y, por lo tanto, el proceso finalizó cuando se devolvió main().
Terminación de subprocesos
Como ya se discutió anteriormente, cada programa comienza con al menos un hilo, que es el hilo en el que se ejecuta la función main(). Entonces, la vida útil máxima de cada subproceso que se ejecuta en el programa es la del subproceso principal. Entonces, si queremos que el subproceso principal espere hasta que todos los demás subprocesos terminen, entonces hay una función pthread_join().
#include <pthread.h> int pthread_join(pthread_t thread, void **rval_ptr);
La función anterior se asegura de que su subproceso principal no finalice hasta que finalice. Esta función se llama desde el subproceso principal y el primer argumento es el ID del subproceso en el que esperar y el segundo argumento es el valor de retorno del subproceso en el que queremos que espere el subproceso principal. Si no estamos interesados en el valor devuelto, podemos configurar este puntero como NULL.
Si clasificamos en un nivel más amplio, vemos que un hilo puede terminar de tres maneras:
- Si el subproceso regresa de su rutina de inicio.
- Si es cancelado por algún otro subproceso. La función utilizada aquí es pthread_cancel().
- Si llama a la función pthread_exit() desde dentro de sí mismo.
El foco aquí estaría en pthread_exit(). Su prototipo es el siguiente:
#include <pthread.h> void pthread_exit(void *rval_ptr);
Entonces vemos que esta función acepta solo un argumento, que es el retorno del hilo que llama a esta función. El subproceso principal accede a este valor de retorno que está esperando que finalice este subproceso. Se puede acceder al valor de retorno del subproceso terminado por la función pthread_exit() en el segundo argumento de pthread_join que se acaba de explicar anteriormente.
Ejemplo de terminación de hilo C
Tomemos un ejemplo en el que usamos las funciones discutidas anteriormente:
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; int ret1,ret2; void* doSomeThing(void *arg) { unsigned long i = 0; pthread_t id = pthread_self(); for(i=0; i<(0xFFFFFFFF);i++); if(pthread_equal(id,tid[0])) { printf("\n First thread processing done\n"); ret1 = 100; pthread_exit(&ret1); } else { printf("\n Second thread processing done\n"); ret2 = 200; pthread_exit(&ret2); } return NULL; } int main(void) { int i = 0; int err; int *ptr[2]; while(i < 2) { err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL); if (err != 0) printf("\ncan't create thread :[%s]", strerror(err)); else printf("\n Thread created successfully\n"); i++; } pthread_join(tid[0], (void**)&(ptr[0])); pthread_join(tid[1], (void**)&(ptr[1])); printf("\n return value from first thread is [%d]\n", *ptr[0]); printf("\n return value from second thread is [%d]\n", *ptr[1]); return 0; }
En el código de arriba:
- Creamos dos hilos usando pthread_create()
- La función de inicio para ambos subprocesos es la misma, es decir, doSomeThing()
- Los subprocesos salen de la función de inicio utilizando la función pthread_exit() con un valor de retorno.
- En la función principal después de que se crean los subprocesos, se llama a las funciones pthread_join() para esperar a que se completen los dos subprocesos.
- Una vez que ambos subprocesos están completos, se accede a su valor de retorno mediante el segundo argumento en la llamada pthread_join().
La salida del código anterior aparece como :
$ ./threads Thread created successfully Thread created successfully First thread processing done Second thread processing done return value from first thread is [100] return value from second thread is [200]
Entonces vemos que ambos subprocesos se ejecutan completamente y se accede a su valor de retorno en la función principal.