posix多线程,POSIX thread
有一个完整的与线程相关的库调用集合,大部分都是以pthread_开头的。要使用这些库调用,我们必须定义macro _REENTRANT,包含文件pthread.h,并使用-lpthread链接线程库。
在设计最初的Unix和POSIX库函数时,假设任何进程中只有一个执行线程。一个明显的例子是errno,它用于在调用失败后获取错误信息。在多线程程序中,默认情况下只有一个变量在线程间共享。一个线程中的调用可以很容易地在另一个线程获得之前的错误代码之前更新这个变量。类似的程序存在于函数中,比如fputs,它通常使用一个全局区域来缓存输出。
我们需要称之为可重入例程。可重入代码可以多次调用,无论是不同线程调用还是嵌入式调用,都可以正常工作。因此,可重入代码部分必须使用局部变量,这样每次调用这段代码都可以获得唯一的数据副本。
在多线程程序中,我们通过在程序中任何#include代码之前定义_REENTRANT宏来通知编译器我们需要这个特性。这将为我们做三件事,我们通常不需要知道为我们做了什么:
有些函数的函数原型等同于可重入函数原型。它们通常有相同的函数名,但是在函数名后面加上_r。例如,gethostbyname成为GetHostByName _ r。
stdio.h中的一些通常作为宏实现的函数变成了合适的可重入安全函数。
errno.h中的变量errno成为调用函数,可以多线程安全的方式确认真正的errno值。
文件contain 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 *(启动例程)(void *)
前面一行只是说明我们必须传递一个带有指向void的指针的函数地址作为参数,这个函数会返回一个指向void的指针。因此,我们可以传递任何类型的单个参数,并返回指向任何类型的指针。使用fork会让程序继续在同一个地方执行,返回代码不同,而使用新线程会在新线程开始执行的地方显式提供一个函数指针。
如果成功,函数将返回0,如果出现错误,将返回错误代码。手册页详细描述了本章中使用的该功能和其他功能的错误情况。
注意:与大多数pthread_functions类似,pthread_create是少数几个不跟-1作为错误代码的Unix函数之一。除非我们非常确定,否则在检测到错误代码之前检查手册页通常是最安全的方法。
当线程结束时,他会调用pthread_exit函数,类似于进程结束时调用exit。该函数结束调用线程并返回一个指向该对象的指针。千万不要用他来返回一个指向局部变量的指针,因为当线程返回的时候,这个变量就不再存在了,这会造成一个严重的bug。pthread_exit函数声明如下:
#include pthread.h
void pthread _ exit(void * retval);
Pthread_join是一个线程函数,相当于集合子进程的等待进程函数。该函数的声明如下:
#include pthread.h
int pthread_join(pthread_t th,void * * thread _ return);
第一个参数是要等待的线程,pthread_create是我们创建的标识符。第二个参数是一个指针,指向线程返回值的指针。类似于pthread_create函数,这个函数也将在成功时返回零,在失败时返回一个错误代码。
实验-一个简单的线程程序
这个程序创建了另一个线程,演示了它与原线程共享变量,并让新线程向原线程返回一个结果。多线程程序不能比这更简单了!以下是thread1.c:
#包含stdio.h
#包括unistd.h
#包含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(“线程创建失败”);
退出(EXIT _ FAILURE);
}
printf(等待线程完成./n’);
res=pthread_join(a_thread,thread _ result);
if(res!=0)
{
perror(“线程加入失败”);
退出(EXIT _ FAILURE);
}
printf(Thread joined,它返回%s/n ,(char *)Thread _ result);
printf(消息现在是%s/n ,消息);
退出(EXIT _ SUCCESS);
}
void *thread_function
{
printf(thread_function正在运行。Argumen是%s/n ,(char *)arg);
睡眠(3);
strcpy(消息,再见);
pthread_exit(感谢占用CPU时间);
}
1要编译这个程序,首先我们需要确保定义了_REENTRANT。在某些系统上,我们还需要定义_POSIX_C_SOURCE,但一般来说这是不必要的。
现在,我们必须确保链接了正确的库。根据我们的系统,NPTL默认可能不是默认,或者在一些旧系统上,取决于内核,它可能根本不可用。幸运的是,本章中的大部分代码独立于所使用的线程库。在作者的系统上,标准的/usr/incluse/pthread . h文件显示他的名字是LinuxThreads,版权日期是1996年,很明显这是一个老库。要获得新的NPTL库,我们需要安装额外的RPM包来提供/usr/include/nptl下的头文件和/usr/lib/nptl下的库。
识别并安装正确的文件后,我们可以使用以下命令编译并链接我们的程序:
$ cc-D _ REENTRANT-I/usr/include/nptl thread 1 . co thread 1-L/usr/lib/nptl-LP thread
4当我们运行这个程序时,我们将得到以下输出:
$ ./线程1
正在等待线程完成.
thread_function正在运行。争论是Hello World
线程加入,它返回感谢您的CPU时间
消息是现在再见!
花些时间理解这个程序是值得的,因为我们用它作为本章中大多数例子的基础。
我们声明了一个原型,它将在线程创建时调用这个函数。
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);
这里我们传递一个等待加入的线程ID和一个指向结果的指针。这个函数会等到另一个函数结束后再返回。然后他会输出线程的返回值和变量的内容,并退出。
新线程开始执行,thread_function将输出其参数,休眠一段时间,更新全局变量,然后退出,向主线程返回一个字符串。新线程会覆盖与原始线程message相同的数组。如果我们调用fork而不是pthread_create,就不会出现这种情况。