POSIX线程(二)


第一个线程程序

有一个完整的与线程相关的库调用集合,其中的大多数名字以pthread_开头。要使用这些库调用,我们必须定义宏_REENTRANT,包含文件pthread.h,并且使用-lpthread来链接线程库。

当设计原始的Unix与POSIX库函数时,假定在任何进程中只有一个执行线程。一个明显的例子就是errno,这个变量用于在调用失败之后获取错误信息。在一个多线程程序中,默认情况下只有一个在线程之间共享的变量。一个线程中的调用可以很容易在另一个线程获取前一个错误代码之前更新这个变量。相似的程序也存在于函数中,例如fputs,通常使用一个全局区域用于缓存输出。

我们需要被称之为可重入的例程。重入代码可以被多次调用,无论是被不同的线程还是被嵌入调用,并且可以正常工作。所以,可重入的代码部分必须使用局部变量,这样对于这个代码的每一个调用都可以获得一份唯一的数据拷贝。

在多线程程序中,我们通过在程序中任何#include代码之前定义_REENTRANT宏来通知编译器我们需要这个特性。这会为我们做三件事情,而我们通常并不需要知道为我们做了什么:

某些函数具有与可重入等同的函数原型。他们通常具有相同的函数名,但是在函数名后添加了_r,例如,gethostbyname就变成了gethostbyname_r。

通常作为宏实现的某些stdio.h中的函数变为了合适的可重入安全函数。

errno.h中的变量errno变成了调用函数,这个函数可以以多线程安全的方式确实真正的errno值。

包含pthread.h文件为我们提供了在我们的代码中所需要的其他定义与原型,类似于用于标准输入与输出例程的stdio.h。最后,我们需要确保我们包含了正确的头文件并且链接实现pthread功能的正确的线程库。我们会在稍后的试验部分了解详细的内容。

pthread_create创建一个新的线程,类似于fork创建一个新的进程。

#include <pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void
*(*start_routine)(void *), void *arg);

这看起来有些复杂,但是实际上很容易使用。第一个参数是一个指向pthread_t的指针。当线程创建后,一个标识就会被写入这个指针所指向的变量。这个标识可以使得我们引用这个线程。下一个参数设置了线程的属性。我们通常并不需要特殊的属性,而且我们可以简单的传递一个NULL作为参数。在本章的后面部分我们会了解如何使用这些属性。最后的两个参数指出了要开始执行的线程函数与要传递给这个函数的参数。

void *(*start_routine)(void *)

上一行简单的表明我们必须传递一个带有void指针作为参数的函数地址,而且这个函数会返回一个指向void的指针。所以我们可以传递任何类型的单个参数,并且返回指向任何类型的指针。使用fork会使得程序在相同的位置使用不同的返回代码继续执行,而使用新线程显式的提供一个新线程开始执行处的函数指针。

如果成功,函数会返回0,如果发生错误则会返回一个错误代码。手册页中有这个函数以及本章中所用的其他函数的错误条件的详细描述。

注:pthread_create,与大多数的pthread_functions类似,是Unix函数中少数几个不遵循-1作为返回错误代码的函数。除非我们非常确定,通常最安全的办法就是在检测错误代码之前查看手册页。

当线程结束时,他会调用pthread_exit函数,类似于进程结束时调用exit。函数结束调用线程,返回一个指向对象的指针。绝不要使用他返回一个指向局部变量的指针,因为当线程返回时,变量将会不再存在,从而会引起一个严重的bug。pthread_exit函数声明如下:

#include <pthread.h>
void pthread_exit(void *retval);

pthread_join是与收集子进程的wait进程函数等同的线程函数。这个函数的声明如下:

#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);

第一个参数是要等待的线程,pthread_create为我们所创建的标识符。第二个参数是一个指向指针的指针,指向由线程返回值的指针。与pthread_create函数类似,这个函数也会在成功时返回零,如果失败则会返回错误代码。

试验--一个简单的线程程序

这个程序创建了另一个线程,演示了他与原始线程共享变量,并且使得新线程向原线程返回一个结果。多线程程序不会比这更简单!下面是thread1.c:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void *thread_function(void *arg);

char message[] = "Hello World";

int main()
{
    int res;
    pthread_t a_thread;
    void *thread_result;

    res = pthread_create(&a_thread,NULL,thread_function,(void *)message);
    if(res != 0)
    {
        perror("Thread creation failed");
        exit(EXIT_FAILURE);
    }
    printf("Waiting for thread to finish.../n");
    res = pthread_join(a_thread,&thread_result);
    if(res != 0)
    {
        perror("Thread join failed");
        exit(EXIT_FAILURE);
    }

    printf("Thread joined, it returned %s/n",(char *)thread_result);
    printf("Message is now %s/n",message);
    exit(EXIT_SUCCESS);
}

void *thread_function(void *arg)
{
    printf("thread_function is running. Argumen was %s/n",(char *)arg);
    sleep(3);
    strcpy(message,"Bye");
    pthread_exit("Thank you for the CPU time");
}

1 要编译这个程序,首先我们需要确保定义了_REENTRANT。在一些系统上,我们也需要定义_POSIX_C_SOURCE,但是通常来说这个并不是必须的。

2 现在我们必须确保链接了正确的库。依据我们的系统,NPTL默认也许并不是默认的,或者在一些老的系统上,依据内核,也许根本就不可用。幸运的是,本章中的大多数代码都是独立于所用的精确线程库的。在作者的系统,检测标准的/usr/incluce/pthread.h文件显示他名为LinuxThreads,版权日期为1996,所以很明显这是一个旧的库。要得到新的NPTL库,我们需要安装额外的RPM包,来在/usr/include/nptl下提供头文件,并且在/usr/lib/nptl下提供库。

3 在标识与安装了正确的文件以后,我们可以用下面的命令来编译与链接我们的程序了:

$ cc -D_REENTRANT -I/usr/include/nptl thread1.c –o thread1 -L/usr/lib/nptl -lpthread

4 当我们运行这个程序时,我们会得到下面的输出:

$ ./thread1
Waiting for thread to finish...
thread_function is running. Argument was Hello World
Thread joined, it returned Thank you for the CPU time
Message is now Bye!

花一些时间来理解这个程序是很值得的,因为我们将其作为本章中大多数例子的基础。

工作原理

我们声明了一个在线程创建时会调用了函数原型。

void *thread_function(void *arg);

正如pthread_create函数所需要的,他会带有一个指向void的指针作为其唯一参数,并且返回一个指向void的指针。我们会在稍后了介绍函数的定义。

在main函数中,我们声明了一些变量,然后调用pthread_create来启动我们新的线程。

pthread_t a_thread;
void *thread_result;
res = pthread_create(&a_thread, NULL, thread_function, (void *)message);

我们传递一个pthread_t对象的地址,我们可以使用他在稍后引用这个线程。我们并不希望修改默认的线程属性,所以我们传递一个NULL作为第二个参数。最后两个参数是要调用的函数以及传递给他的参数。

如果调用成功,现在就会运行两个线程。原始线程(main)继续执行并且执行pthread_create之后的代码,并且一个新线程开始执行thread_function代码。

原始线程检测新线程已经开始,然后调用pthread_join。

res = pthread_join(a_thread, &thread_result);

在这里我们传递一个我们等待联合的线程标识以及一个指向结果的指针。这个函数会一直等待,直到另一个在他返回之前结束。然后他会输出线程的返回值以及变量的内容,并退出。

新线程开始执行并且thread_function会输出其参数,休眠一段时间,更新全局变量,然后退出,向主线程返回一个字符串。新线程改写与原始线程访问相同的数组,message。如果我们调用fork而不是pthread_create则不会这样。

posted @ 2009-07-05 10:32  jlins  阅读(213)  评论(0编辑  收藏  举报