我们知道,对多进程的程序而言。如果子进程结束后父进程还未结束,那么该子进程将会处于僵尸状态,我们称之为“僵进程”。对于僵进程,直到父进程调用wait()或waitpid()函数后,僵进程的状态才会被解除,此时子进程的资源才得以释放。
1.僵线程
对线程而言,同样有类似于“僵线程”的概念。比如,主线程(或任何其他一个线程)没有对即将结束的子线程调用pthread_join()函数,当该线程结束之后就会处于僵线程状态。
僵线程状态下的线程同样面临着一个问题:线程已经结束,但是线程特有的资源(如线程ID,栈空间等)却没有释放。
2.线程连接(join)
僵线程的资源得不到及时的释放,势必会影响系统资源的分配效率。通过线程连接(pthread_join()函数)可以避免僵线程(主线程会等待从线程结束,并回收从线程的资源),pthread_join()函数原型如下:
1 |
int pthread_join(pthread_t thread, void **retval) |
其中,参数thread指明被连接的线程对象;retval用来接收被连接线程结束时的返回值,若不关注可以置为NULL。
但是,解决了僵线程之后,问题又来了:当主线程A调用pthread_join()函数连接从线程B之后,在B没有运行结束之前A线程会一直阻塞。有如下示例:
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 |
#include <stdio.h> #include <stdlib.h> //for exit() #include <string.h> //for strerror() #include <unistd.h> #include <pthread.h> void *start_routine(void *arg) { printf("I am B.\n"); while(1){/*do something.*/}; printf("B exit...\n"); return 0; } int main() { pthread_t tid; int ret; ret = pthread_create(&tid, NULL, start_routine, NULL); if(ret != 0){ fprintf(stderr, "pthread_create:%s\n", strerror(ret)); exit(EXIT_FAILURE); } printf("I am A.\n"); pthread_join(tid,NULL); /*主线程阻塞,后面的操作无法进行*/ printf("A exit...\n"); return 0; } |
编译、运行:
1 2 3 4 |
[root@localhost learn-thread]# gcc -Wall -g join_test.c -o join_test -lpthread [root@localhost learn-thread]# ./join_test I am A. I am B. |
主线程A会一直阻塞,如果A在pthread_join()之后还有其他业务需要处理的话(比如监听连接等等),这就是一种有缺陷的设计。
3.线程分离(detach)
虽然pthread_join()解决了僵线程的问题,但同时也带来了主线程阻塞的附加问题。那么,有没有一种既不会让从线程沦为僵线程,也不会让主线程阻塞的方案呢?
将线程设置为分离状态,可以在避免僵线程的同时不会阻塞主线程。设置线程分离有两种方法:一种是在创建线程时设置线程属性为分离的;另一种是调用pthread_detach()函数完成线程分离属性的设置(具体接口可参考man手册)。
有如下示例:
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 |
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> void *start_routine(void *arg) { int i; for(i=0; i<10; i++){ printf("B"); fflush(stdout); } printf("\n"); printf("B exit...\n"); return 0; } int main() { pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); int ret; ret = pthread_create(&tid,&attr,start_routine,NULL); if(ret !=0 ){ fprintf(stderr, "pthread_create:%s", strerror(ret)); exit(EXIT_FAILURE); } int i; for(i=0; i<10; i++){ printf("A"); fflush(stdout); } printf("\n"); usleep(1000); //pthread_exit(NULL); printf("A exit...\n"); return 0; } |
编译、运行:
1 2 3 4 5 6 |
[root@localhost learn-thread]# gcc -Wall -g detach_test.c -o detach_test -lpthread [root@localhost learn-thread]# ./detach_test AAAAAAAAAA BBBBBBBBBB B exit... A exit... |
此时,B线程在运行结束之后会自动释放资源,A线程也不会阻塞。但同时也会带来另一个问题:上述示例中,我有意的将A线程休眠了1毫秒,如果将usleep(1000)注释掉的话,主线程A执行完后会直接退出,分离状态的B线程将不会运行:
1 2 3 |
[root@localhost learn-thread]# ./detach_test AAAAAAAAAA A exit... |
这是因为在main()函数内的主线程一旦退出,进程就退出了,其他线程也会随之结束。所以,对于存在分离状态的程序设计,你必须通过某种手段使主线程一直运行直到其他线程运行结束。当然,你也可以在main()函数内调用pthread_exit()函数,这会使得进程在所有线程运行结束之后才会退出。
4.其他
任何一种特性的实现都是为了解决某一特定问题,但同时往往会带来一定的附加问题。所以,不管线程的状态是joined还是detached,在充分了解其内部原理之后,做到尽可能的扬长避短就够了。