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

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 系统运维

2012-03-31 22:18:16

传统的UNIX进程模型每个进程只支持一个线程控制。概念上地,这和基于线程的模型是相同的,只是每个进程只由一个线程组成。有了 pthreads,当一个程序运行时,它也作为带有单个线程控制的单个进程启动。当程序运行时,它的行为应该和传统的进程没有区别,直到它创建了更多的线 程控制。额外的线程可以通过调用pthread_create函数创建。



  1. #include <pthread.h>

  2. int pthread_create(pthread *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg);

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


tidp所指的内存地址被设置为新创建的线程的线程ID,当pthread_create成功返回时。attr参数被用作定制各种线程属性。我们将在12.3节覆盖线程属性,但是现在,我们将把它设为NULL来创建一个有默认属性的线程。


新创建的线程在start_rtn函数的地址开始运行。这个函数接受单个参数,arg,它是无类型的指针。如果你需要传递多于一个的参数给start_rtn函数,那么你需要把它们存储在一个结构体里并在arg里传递这个结构体的地址。


当一个线程被创建时,没有保证哪个先运行:新创建的线程还是调用线程。新创建的线程有进程地址空间的访问,并继承了调用线程的浮点环境和信号掩码;然而,线程的待定信号集被清空。


注 意pthread函数通常返回一个错误码,当它们失败时。它们不像其它POSIX函数一样设置errno。每个线程的errno的拷贝只被提供来与已有的 使用它的函数兼容。使用线程,从函数返回错误码更简洁,因而把错误的范围局限在导致它的函数里,而不是依赖于一些作为函数副作用改变的全局数据。


尽管没有可移植的方法来打印线程ID,然而我们可以写一个小的测试程序来打印,来得到进程如何工作的一些观察。下面的代码创建一个线程并打印新线程和初始线程的进程ID和线程ID。



  1. #include <pthread.h>

  2. pthread_t ntid;

  3. void
  4. printids(const char *s)
  5. {
  6.     pid_t pid;
  7.     pthread_t tid;

  8.     pid = getpid();
  9.     tid = pthread_self();
  10.     printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,
  11.         (unsigned int)tid, (unsigned int)tid);
  12. }

  13. void *
  14. thr_fn(void *arg)
  15. {
  16.     printids("new thread: ");
  17.     return((void *)0);
  18. }

  19. int
  20. main(void)
  21. {
  22.     int err;

  23.     err = pthread_create(&ntid, NULL, thr_fn, NULL);
  24.     if (err != 0) {
  25.         printf("can't create thread: %s\n", strerror(err));
  26.         exit(1);
  27.     }
  28.     printids("main thread: ");
  29.     sleep(1);
  30.     exit(0);
  31. }

编译时需要用gcc -lpthread选项来引用该库,否则会得到undefined reference to `pthread_create'的错误。


这个例子有两件怪事,需要在主线程和新线程之间处理竞争。(我们在本章后面学习处理这些的更好的方式。)第一个是主线程睡眠的需要。如果它不睡,主线程可能退出,因而终止整个进程,在新线程有机会运行前。这个行为取决于操作系统的线程实现和调度算法。


第 二个怪事是新线程通常调用pthread_self来得到它的线程ID而不是从共享的内存读取或作为它线程开始全程的一个参数来获得。回想下 pthread_create将返回新建线程的ID,通过第一个参数(tidp)。在我们的例子里,主线程把它存在ntid里,但是新线程不能安全地用 它。如果新线程在主线程从pthread_create调用返回前运行,那么新线程将看到ntid的未初始化的内容而不是线程ID。


Solaris上的运行结果为:
$ ./a.out
main thread:  pid 7225 tid 1 (0x1)
new thread:  pid 7225 tid 4 (0x4)

正如我们期望的,两个线程有相同的进程ID,但是是不同的线程ID。


FreeBSD上的结果为:
$ ./a.out
main thread:  pid 14954 tid 134529024 (0x804c000)
new thread:  pid 14954 tid 134530048 (0x804c400)

正如我们期望的,两个线程有相同的进程ID。如果我们把线程ID看到十进制整数,那么值看起来很奇怪,但是如果我们用16进制看它们,那么它们看起来更有意义。正如我们早先注明的,FreeBSD使用线程数据结构体的指针表示它的线程ID。


我们期望Mac OS X和FreeBSD相似,但是主线程的线程ID和用pthread_create创建的线程的ID在不同的地址范围:
$ ./a.out
main thread:  pid 779 tid 2684396012 (0xa000a1ec)
new thread:  pid 779 tid 25166336 (0x1800200)


在Linux上运行相同的程序稍微有些不同:
$ ./a.out
new thread:  pid 6628 tid 1026 (0x402)
main thread:  pid 6626 tid 1024 (0x400)


Linux线程ID看起来更合理,但是进程ID不匹配。这是Linux线程实现的人工制品,这里clone系统调用被用来实现pthread_create。clone系统调用创建一个子进程,可以共享它父进程执行上下文的可配置的量,比如文件描述符和内存。


还要注意主线程的输出在创建的线程的输出前出现,除了Linux。这证明了我们不能对线程如何被调度作任何假设。

(我机器上的Linux的结果为:
$ ./a.out
main thread:  pid 2381 tid 3077965504 (0xb77606c0)
new thread:  pid 2381 tid 3077962608 (0xb775fb70)。)

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