Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1744492
  • 博文数量: 438
  • 博客积分: 9799
  • 博客等级: 中将
  • 技术积分: 6092
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-25 17:25
文章分类

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 系统运维

2012-03-31 22:22:08

如果进程里的任何线程调用exit、_Exit或_exit,那么整个进程会终止。相似的,当默认的动作被设为终止进程时,一个发送给一个线程的信号将终止整个进程(我们在12.8节讨论更多关于信号和线程之间的交互)。


单个线程可以用三种方式退出,从而停止它的控制流,而不终止整个进程。


1、线程可以简单地从开始例程退出。返回值是线程的退出码。


2、线程可以被同一进程的其它线程取消。


3、线程可以调用pthread_exit。



  1. #include <pthread.h>

  2. void pthread_exit(void *rval_ptr);

rval_ptr是无类型指针,和传入启动例程的单个参数相似。这个指针对进程里的其它线程可用,通过调用pthread_join函数。


  1. #include <pthread.h>

  2. int pthread_join(pthread_t thread, void **rval_ptr);

  3. 成功返回0,失败返回错误码。


调用线程将阻塞,直到指定的线程调用pthread_exit、从它的启动例程退出、或被取消。如果线程简单地从它的启动例程返回,那么rval_ptr将包含返回码。如果线程被取消,那么由rval_ptr指定的内存地址被设为PTHREAD_CANCELED。


通过调用pthread_join,我们自动把一个线程置为分离状态(马上讨论)以便它的资源可以被恢复。如果线程已经在分离状态了,那么调用pthread_join失败,返回EINVAL。


如果我们不对一个线程的返回值感兴趣,我们可以把rval_ptr设为NULL。在这种情况下,调用pthread_join允许我们等待指定的线程,但不得到线程的终止状态。


下面的代码展示了如何获取一个终止线程的退出码。



  1. #include <pthread.h>

  2. void *
  3. thr_fn1(void *arg)
  4. {
  5.     printf("thread 1 returning\n");
  6.     return((void *)1);
  7. }

  8. void *
  9. thr_fn2(void *arg)
  10. {
  11.     printf("thread 2 exiting\n");
  12.     pthread_exit((void *)2);
  13. }

  14. int
  15. main(void)
  16. {
  17.     int err;
  18.     pthread_t tid1, tid2;
  19.     void *tret;

  20.     err = pthread_create(&tid1, NULL, thr_fn1, NULL);
  21.     if (err != 0) {
  22.         printf("can't create thread 1: %s\n", strerror(err));
  23.         exit(1);
  24.     }
  25.     err = pthread_create(&tid2, NULL, thr_fn2, NULL);
  26.     if (err != 0) {
  27.         printf("can't create thread 2: %s\n", strerror(err));
  28.         exit(1);
  29.     }
  30.     err = pthread_join(tid1, &tret);
  31.     if (err != 0) {
  32.         printf("can't join with thread 1: %s\n", strerror(err));
  33.         exit(1);
  34.     }
  35.     printf("thread 1 exit code %d\n", (int)tret);
  36.     err = pthread_join(tid2, &tret);
  37.     if (err != 0) {
  38.         printf("can't join with thread 2: %s\n", strerror(err));
  39.         exit(1);
  40.     }
  41.     printf("thread 2 exit code %d\n", (int)tret);
  42.     exit(0);
  43. }

运行结果为:
$ ./a.out
thread 1 returning
thread 2 exiting
thread 1 exit code 1
thread 2 exit code 2

正如我们可以看到的,当一个线程通过调用pthread_exit退出或简单从启动例程返回时,退出状态可以被另一个线程通过调用pthread_join来获得。


传 递给pthread_create和pthread_exit的无类型的指针可以被用来传递比单个值更多。这个指针可以用来传递一个包含更复杂信号的结构 体的地址。小心当调用者完成时用作结构体的指针仍然有效。如果结构体在调用者的栈上分配,那么内存内容可能在结构体被使用时已经改变了。例如,如果一个线 程在它的栈上分配了一个结构体并把它的指针传递给pthread_exit,那么然后这个栈可能被销毁而它的内存被重用为其它东西,在 pthread_join尝试使用它时。


下面的代码展示了使用一个自动变量(在栈上分配)作为pthread_exit的参数的问题。



  1. #include <pthread.h>

  2. struct foo {
  3.     int a, b, c, d;
  4. };

  5. void
  6. printfoo(const char *s, const struct foo *fp)
  7. {
  8.     printf(s);
  9.     printf(" structure at 0x%x\n", (unsigned)fp);
  10.     printf(" foo.a = %d\n", fp->a);
  11.     printf(" foo.b = %d\n", fp->b);
  12.     printf(" foo.c = %d\n", fp->c);
  13.     printf(" foo.d = %d\n", fp->d);
  14. }

  15. void *
  16. thr_fn1(void *arg)
  17. {
  18.     struct foo foo = {1, 2, 3, 4};

  19.     printfoo("thread 1:\n", &foo);
  20.     pthread_exit((void *)&foo);
  21. }

  22. void *
  23. thr_fn2(void *arg)
  24. {
  25.     printf("thread 2: ID is %d\n", pthread_self());
  26.     pthread_exit((void *)0);
  27. }

  28. int
  29. main(void)
  30. {
  31.     int err;
  32.     pthread_t tid1, tid2;
  33.     struct foo *fp;

  34.     err = pthread_create(&tid1, NULL, thr_fn1, NULL);
  35.     if (err != 0) {
  36.         printf("can't create thread 1:%s\n", strerror(err));
  37.         exit(1);
  38.     }
  39.     err = pthread_join(tid1, (void *)&fp);
  40.     if (err != 0) {
  41.         printf("can't join with thread 1:%s\n", strerror(err));
  42.         exit(1);
  43.     }
  44.     sleep(1);
  45.     printf("parent starting second thread\n");
  46.     err = pthread_create(&tid2, NULL, thr_fn2, NULL);
  47.     if (err != 0) {
  48.         printf("can't create thread 2:%s\n", strerror(err));
  49.         exit(1);
  50.     }
  51.     sleep(1);
  52.     printfoo("parent:\n", fp);
  53.     exit(0);
  54. }

运行结果为:
thread 1:
 structure at 0xb77dd380
 foo.a = 1
 foo.b = 2
 foo.c = 3
 foo.d = 4
parent starting second thread
thread 2: ID is -1216488592
parent:
 structure at 0xb77dd380
 foo.a = 0
 foo.b = 2423756
 foo.c = 2768884
 foo.d = 2768884

当然,根据内存架构、编译器和线程库的实现,结果会有所不同。


正如我们可以看到的,结构体的内容(在线程tid1的栈上分配的)在主线程可以访问这个结构体时已经改变了。注意第二个线程(tid2)的栈如何覆写掉第一个线程的栈。为了解决这个问题,我们可以使用一个全局结构体或用malloc分配这个结构体。


一个线程可以请求同一进程内的另一个线程取消,通过调用pthread_cancel函数。



  1. #include <pthread.h>

  2. int pthread_cancel(pthread_t tid);

  3. 成功返回0,失败返回错误号。


在 默认情况下,pthread_cancel将导致tid指定的线程表现得好像它已经调用参数为PTHREAD_CANCELED的 pthread_exit。然而,一个线程可以选择忽略或控制它如何被取消。我们将在12.7节深入讨论。注意pthread_cancel不等待线程终 止。它只是发出请求。


一个线程可以安排当它退出时要调用的函数,和atexit函数(7.3节)安排在进程退出时所调用的函数的方法相似。这个函数被称为线程清理处理机。一个线程可以建立多个清理处理机。处理机被保存在一个栈里,这意味着它们以被注册的相反的顺序执行。



  1. #include <pthread.h>

  2. void pthread_cleanup_push(void (*rtn)(void *), void *arg);

  3. void pthread_cleanup_pop(int execute);


pthread_cleanup_push函数安排清理函数,rtn,随着单个参数,arg,而被调用,当线程执行以下某个动作时:

1、调用pthread_exit;

2、响应一个取消请求;

3、使用非0execute参数调用pthread_cleanup_pop。


如果execute参数被设为0,那么清理函数不会被调用。在各种情况下,pthread_cleanup_pop都删除由最后的pthraed_cleanup_push调用建立的清理处理机。


这些函数的一个限制是,因为它们可以被实现为宏,所以它们必须在线程的一些范围内成对地使用。pthread_cleanup_push的宏定义可以包括一个“{”字符,这种情况下要匹配pthread_cleanup_pop定义里的“}”字符。


下面的代码展示如何使用线程清理处理机。尽管这个例子有点勉强,但它展示了所使用的机制。注意尽管我们从未想要传一个非0参数给线程启动例程,但我们仍需要匹配pthread_cleanup_pop和pthread_cleanup_push;否则,程序可能不能编译。



  1. #include <pthread.h>

  2. void
  3. cleanup(void *arg)
  4. {
  5.     printf("cleanup: %s\n", (char *)arg);
  6. }

  7. void *
  8. thr_fn1(void *arg)
  9. {
  10.     printf("thread 1 start\n");
  11.     pthread_cleanup_push(cleanup, "thread 1 first handler");
  12.     pthread_cleanup_push(cleanup, "thread 1 second handler");
  13.     printf("thread 1 push complete\n");
  14.     if (arg)
  15.         return ((void *)1);
  16.     pthread_cleanup_pop(0);
  17.     pthread_cleanup_pop(0);
  18.     return ((void*)1);
  19. }

  20. void *
  21. thr_fn2(void *arg)
  22. {
  23.     printf("thread 2 start\n");
  24.     pthread_cleanup_push(cleanup, "thread 2 first handler");
  25.     pthread_cleanup_push(cleanup, "thread 2 second handler");
  26.     printf("thread 2 push complete\n");
  27.     if (arg)
  28.         pthread_exit((void *)2);
  29.     pthread_cleanup_pop(0);
  30.     pthread_cleanup_pop(0);
  31.     pthread_exit((void *)2);
  32. }

  33. int
  34. main(void)
  35. {
  36.     int err;
  37.     pthread_t tid1, tid2;
  38.     void *tret;

  39.     err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
  40.     if (err != 0) {
  41.         printf("can't create thread 1: %s\n", strerror(err));
  42.         exit(1);
  43.     }
  44.     err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
  45.     if (err != 0) {
  46.         printf("can't create thread 2: %s\n", strerror(err));
  47.         exit(1);
  48.     }
  49.     err = pthread_join(tid1, &tret);
  50.     if (err != 0) {
  51.         printf("can't join with thread 1: %s\n", strerror(err));
  52.         exit(1);
  53.     }
  54.     printf("thread 1 exit code %d\n", (int)tret);
  55.     err = pthread_join(tid2, &tret);
  56.     if (err != 0) {
  57.         printf("can't join with thread 2: %s\n", strerror(err));
  58.         exit(1);
  59.     }
  60.     printf("thread 2 exit code %d\n", (int)tret);
  61.     exit(0);
  62. }

运行结果为:
$ ./a.out
thread 1 start
thread 1 push complete
thread 1 exit code 1
thread 2 start
thread 2 push complete
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 2 exit code 2

从输出我们可以看到两个线程都恰当地启动并退出,但是只有第二个线程的清理处理机被调用。因而,如果线程从它的主例程中返回,那么它的清理处理机不会被调用。还有注意清理处理机以它们被安装里的相当顺序被调用。


到现在为止,你应该开始看到线程函数和进程函数之间的相似之处了。下表总结了这些相似的函数:


进程和线程原始例程的比较
进程原始例程线程原始例程描述
forkpthread_create创建一个新的控制流
exitpthread_exit从已有的控制流中退出
waitpidpthread_join从控制流得到退出状态
atexitpthread_cancel_push注册在控制流退出时调用的函数
getpidpthread_self得到控制流的ID
abortpthread_cancel请求控制流的异常退出


默认时,一个线程的终止状态被保留,直到pthread_join为那个线程调用。一个线程的底下存储可以在终止时立即回收,如果线程被分离。当一个线程被 分离时,pthread_join函数不能用来等待它的终止状态。一个未分离线程而调用的pthread_join会失败,返回EINVAL。我们可以调 用pthread_detach来分离一个线程。



  1. #include <pthread.h>

  2. int pthread_detach(pthread_t tid);

  3. 成功返回0,失败返回错误号。


正如我们将在下章看到的,我们可以创建一个处在分离状态的线程,通过仅修改传入pthread_create的线程属性。

阅读(1377) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

wojiushiwolh2017-02-28 23:27:14

线程从启动例程返回可以通过return、exit、_exit,对吗?