在传统UNIX进程模型中,每个进程只有一个控制线程。从概念上讲,这与基于线程的模型中每个进程只包含一个线程是相同的。在POSIX线程(pthread)的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的。在创建多个控制线程以前,程序的行为与传统的进程并没有什么区别。新增的线程可以通过调用pthread_create函数创建。

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
                                const pthread_attr_t *restrict attr,
                                        void *(*start_rtn)(void *), void *restrict arg);
//返回值:若成功,返回0;否则,返回错误编号

pthread_create成功返回时,新创建线程的线程ID会被设置成tidp指向的内存单元。attr参数用于定制各种不同的线程属性。我们将在12.3节中讨论线程属性,但现在我们把它置为NULL,创建一个具有默认属性的线程。

新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数有一个以上,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

线程创建时并不能保证哪个线程会先运行:是新创建的线程,还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。

注意,pthread 函数在调用失败时通常会返回错误码,它们并不像其他的 POSIX 函数一样设置errno。每个线程都提供errno的副本,这只是为了与使用errno的现有函数兼容。在线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局状态,这样可以把错误的范围限制在引起出错的函数中。

实例

虽然没有可移植的打印线程 ID 的方法,但是可以写一个小的测试程序来完成这个任务,以便更深入地了解线程是如何工作的。图 11-2 中的程序创建了一个线程,打印了进程 ID、新线程的线程ID以及初始线程的线程ID。

#include "apue.h"
#include <pthread.h>

pthread_t ntid;

void
printids(const char *s)
{
    pid_t        pid;
    pthread_t    tid;

    pid = getpid();
    tid = pthread_self();
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,
      (unsigned long)tid, (unsigned long)tid);
}

void *
thr_fn(void *arg)
{
    printids("new thread: ");
    return((void *)0);
}

int
main(void)
{
    int        err;

    err = pthread_create(&ntid, NULL, thr_fn, NULL);
    if (err != 0)
        err_exit(err, "can't create thread");
    printids("main thread:");
    sleep(1);
    exit(0);
}

图11-2 打印线程ID

这个实例有两个特别之处,需要处理主线程和新线程之间的竞争。(我们将在这章后面的内容中学习如何更好地处理这种竞争。)第一个特别之处在于,主线程需要休眠,如果主线程不休眠,它就可能会退出,这样新线程还没有机会运行,整个进程可能就已经终止了。这种行为特征依赖于操作系统中的线程实现和调度算法。

第二个特别之处在于新线程是通过调用pthread_self函数获取自己的线程ID的,而不是从共享内存中读出的,或者从线程的启动例程中以参数的形式接收到的。回忆 pthread_create函数,它会通过第一个参数(tidp)返回新建线程的线程ID。在这个例子中,主线程把新线程ID存放在 ntid 中,但是新建的线程并不能安全地使用它,如果新线程在主线程调用pthread_create返回之前就运行了,那么新线程看到的是未经初始化的ntid的内容,这个内容并不是正确的线程ID。

在Solaris上运行图11-2中的程序,得到:

$ ./a.out
main thread: pid 20075 tid 1 (0x1)
new thread: pid 20075 tid 2 (0x2)

正如我们期望的,两个线程的进程ID相同,但线程ID不同。在FreeBSD上运行图11-2中的程序,得到:

$ ./a.out
main thread: pid 37396 tid 673190208 (0x28201140)
new thread: pid 37396 tid 673280320 (0x28217140)

也如我们期望的,两个线程有相同的进程ID。如果把线程ID看成是十进制整数,那么这两个值看起来很奇怪,但是如果把它们转化成十六进制,看起来就更合理了。就像前面提到的,FreeBSD使用指向线程数据结构的指针作为它的线程ID。

我们期望Mac OS X与FreeBSD相似,但事实上,在MacOS X中,主线程ID与用pthread_create新创建的线程的线程ID不在相同的地址范围内:

$ ./a.out
main thread: pid 31807 tid 140735073889440(0x7fff70162ca0)
new thread: pid 31807 tid 4295716864(0x1000b7000)

相同的程序在Linux上运行得到:

$ ./a.out
main thread: pid 17874 tid 140693894424320(0x7ff5d9996700)
new thread: pid 17874 tid 140693886129920(0x7ff5d91ad700)

尽管Linux线程ID是用无符号长整型来表示的,但是它们看起来像指针。

Linux 2.4和Linux 2.6在线程实现上是不同的。Linux 2.4中,LinuxThreads是用单独的进程实现每个线程的,这使得它很难与POSIX线程的行为匹配。Linux 2.6中,对Linux内核和线程库进行了很大的修改,采用了一个称为Native POSIX线程库(Native POSIX Thread Library,NPTL)的新线程实现。它支持单个进程中有多个线程的模型,也更容易支持POSIX线程的语义。

results matching ""

    No results matching ""