分类: 系统运维
2012-03-31 22:18:16
传统的UNIX进程模型每个进程只支持一个线程控制。概念上地,这和基于线程的模型是相同的,只是每个进程只由一个线程组成。有了 pthreads,当一个程序运行时,它也作为带有单个线程控制的单个进程启动。当程序运行时,它的行为应该和传统的进程没有区别,直到它创建了更多的线 程控制。额外的线程可以通过调用pthread_create函数创建。
tidp所指的内存地址被设置为新创建的线程的线程ID,当pthread_create成功返回时。attr参数被用作定制各种线程属性。我们将在12.3节覆盖线程属性,但是现在,我们将把它设为NULL来创建一个有默认属性的线程。
新创建的线程在start_rtn函数的地址开始运行。这个函数接受单个参数,arg,它是无类型的指针。如果你需要传递多于一个的参数给start_rtn函数,那么你需要把它们存储在一个结构体里并在arg里传递这个结构体的地址。
当一个线程被创建时,没有保证哪个先运行:新创建的线程还是调用线程。新创建的线程有进程地址空间的访问,并继承了调用线程的浮点环境和信号掩码;然而,线程的待定信号集被清空。
注 意pthread函数通常返回一个错误码,当它们失败时。它们不像其它POSIX函数一样设置errno。每个线程的errno的拷贝只被提供来与已有的 使用它的函数兼容。使用线程,从函数返回错误码更简洁,因而把错误的范围局限在导致它的函数里,而不是依赖于一些作为函数副作用改变的全局数据。
尽管没有可移植的方法来打印线程ID,然而我们可以写一个小的测试程序来打印,来得到进程如何工作的一些观察。下面的代码创建一个线程并打印新线程和初始线程的进程ID和线程ID。
这个例子有两件怪事,需要在主线程和新线程之间处理竞争。(我们在本章后面学习处理这些的更好的方式。)第一个是主线程睡眠的需要。如果它不睡,主线程可能退出,因而终止整个进程,在新线程有机会运行前。这个行为取决于操作系统的线程实现和调度算法。
第 二个怪事是新线程通常调用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)。)