pthread 接口允许我们通过设置每个对象关联的不同属性来细调线程和同步对象的行为。通常,管理这些属性的函数都遵循相同的模式。
- 每个对象与它自己类型的属性对象进行关联(线程与线程属性关联,互斥量与互斥量属性关联,等等)。一个属性对象可以代表多个属性。属性对象对应用程序来说是不透明的。这意味着应用程序并不需要了解有关属性对象内部结构的详细细节,这样可以增强应用程序的可移植性。取而代之的是,需要提供相应的函数来管理这些属性对象。
- 有一个初始化函数,把属性设置为默认值。
- 还有一个销毁属性对象的函数。如果初始化函数分配了与属性对象关联的资源,销毁函数负责释放这些资源。
- 每个属性都有一个从属性对象中获取属性值的函数。由于函数成功时会返回0,失败时会返回错误编号,所以可以通过把属性值存储在函数的某一个参数指定的内存单元中,把属性值返回给调用者。
- 每个属性都有一个设置属性值的函数。在这种情况下,属性值作为参数按值传递。
在第11章所有调用pthread_create
函数的实例中,传入的参数都是空指针,而不是指向pthread_attr_t
结构的指针。可以使用pthread_attr_t
结构修改线程默认属性,并把这些属性与创建的线程联系起来。可以使用pthread_attr_init
函数初始化pthread_attr_t
结构。在调用pthread_attr_init
以后,pthread_attr_t
结构所包含的就是操作系统实现支持的所有线程属性的默认值。
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
如果要反初始化pthread_attr_t
结构,可以调用pthread_attr_destroy
函数。如果pthread_attr_init
的实现对属性对象的内存空间是动态分配的,pthread_attr_destroy
就会释放该内存空间。除此之外,pthread_attr_destroy
还会用无效的值初始化属性对象,因此,如果该属性对象被误用,将会导致pthread_create
函数返回错误码。
图 12-3 总结了 POSIX.1 定义的线程属性。POSIX.1 还为线程执行调度(Thread Execution Scheduling)选项定义了额外的属性,用以支持实时应用,但我们并不打算在这里讨论这些属性。图12-3同时给出了各个操作系统平台对每个线程属性的支持情况。
图12-3 POSIX.1线程属性
11.5节介绍了分离线程的概念。如果对现有的某个线程的终止状态不感兴趣的话,可以使用pthread_detach
函数让操作系统在线程退出时收回它所占用的资源。
如果在创建线程时就知道不需要了解线程的终止状态,就可以修改 pthread_attr_t
结构中的detachstate
线程属性,让线程一开始就处于分离状态。可以使用 pthread_attr_setdetach-state
函数把线程属性detachstate
设置成以下两个合法值之一:
PTHREAD_CREATE_DETACHED
,以分离状态启动线程;PTHREAD_CREATE_JOINABLE
,正常启动线程,应用程序可以获取线程的终止状态。
#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t*restrict attr,int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,int *detachstate);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
可以调用pthread_attr_getdetachstate
函数获取当前的detachstate
线程属性。第二个参数所指向的整数要么设置成PTHREAD_CREATE_DETACHED
,要么设置成 PTHREAD_CREATE_JOINABLE
,具体要取决于给定pthread_attr_t
结构中的属性值。
实例
图12-4给出了一个以分离状态创建线程的函数。
#include "apue.h"
#include <pthread.h>
int
makethread(void *(*fn)(void *), void *arg)
{
int err;
pthread_t tid;
pthread_attr_t attr;
err = pthread_attr_init(&attr);
if (err != 0)
return(err);
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (err == 0)
err = pthread_create(&tid, &attr, fn, arg);
pthread_attr_destroy(&attr);
return(err);
}
图12-4 以分离状态创建线程
注意,此例忽略了pthread_attr_destroy
函数调用的返回值。在这个实例中,我们对线程属性进行了合理的初始化,因此pthread_attr_destroy
应该不会失败。但是,如果pthread_attr_destroy
确实出现了失败的情况,将难以清理:必须销毁刚刚创建的线程,也许这个线程可能已经运行,并且与pthread_attr_destroy
函数可能是异步执行的。忽略pthread_attr_destroy
的错误返回可能出现的最坏情况是,如果pthread_attr_init
已经分配了内存空间,就会有少量的内存泄漏。另一方面,如果 pthread_attr_init
成功地对线程属性进行了初始化,但之后pthread_attr_destroy
的清理工作失败,那么将没有任何补救策略,因为线程属性结构对应用程序来说是不透明的,可以对线程属性结构进行清理的唯一接口是pthread_attr_destroy
,但它失败了。
对于遵循POSIX标准的操作系统来说,并不一定要支持线程栈属性,但是对于遵循Single UNIX Specification 中 XSI 选项的系统来说,支持线程栈属性就是必需的。可以在编译阶段使用_POSIX_THREAD_ATTR_STACKADDR
和_POSIX_THREAD_ATTR_STACKSIZE
符号来检查系统是否支持每一个线程栈属性。如果系统定义了这些符号中的一个,就说明它支持相应的线程栈属性。或者,也可以在运行阶段把_SC_THREAD_ATTR_ STACKADDR
和_SC_THREAD_ATTR_STACKSIZE
参数传给sysconf
函数,检查运行时系统对线程栈属性的支持情况。
可以使用函数pthread_attr_getstack
和pthread_attr_setstack
对线程栈属性进行管理。
#include <pthread.h>
int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr,
size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr,
void *stackaddr, size_t stacksize);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
对于进程来说,虚地址空间的大小是固定的。因为进程中只有一个栈,所以它的大小通常不是问题。但对于线程来说,同样大小的虚地址空间必须被所有的线程栈共享。如果应用程序使用了许多线程,以致这些线程栈的累计大小超过了可用的虚地址空间,就需要减少默认的线程栈大小。另一方面,如果线程调用的函数分配了大量的自动变量,或者调用的函数涉及许多很深的栈帧(stack frame),那么需要的栈大小可能要比默认的大。
如果线程栈的虚地址空间都用完了,那可以使用malloc
或者mmap
(见14.8节)来为可替代的栈分配空间,并用pthread_attr_setstack
函数来改变新建线程的栈位置。由stackaddr
参数指定的地址可以用作线程栈的内存范围中的最低可寻址地址,该地址与处理器结构相应的边界应对齐。当然,这要假设malloc
和mmap
所用的虚地址范围与线程栈当前使用的虚地址范围不同。
stackaddr
线程属性被定义为栈的最低内存地址,但这并不一定是栈的开始位置。对于一个给定的处理器结构来说,如果栈是从高地址向低地址方向增长的,那么stackaddr
线程属性将是栈的结尾位置,而不是开始位置。
应用程序也可以通过pthread_attr_getstacksize
和pthread_attr_setstacksize
函数读取或设置线程属性stacksize
。
#include <pthread.h>
int pthread_attr_getstacksize(const pthread_attr_t*restrict attr, size_t *restrict stacksize);
int pthread_attr_setstacksize (pthread_attr_t *attr,size_t stacksize);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
如果希望改变默认的栈大小,但又不想自己处理线程栈的分配问题,这时使用pthread_attr_setstacksize
函数就非常有用。设置stacksize
属性时,选择的stacksize
不能小于PTHREAD_STACK_MIN
。
线程属性guardsize
控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小。这个属性默认值是由具体实现来定义的,但常用值是系统页大小。可以把guardsize
线程属性设置为0,不允许属性的这种特征行为发生:在这种情况下,不会提供警戒缓冲区。同样,如果修改了线程属性stackaddr
,系统就认为我们将自己管理栈,进而使栈警戒缓冲区机制无效,这等同于把guardsize
线程属性设置为0。
#include <pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t*restrict attr,size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
如果guardsize
线程属性被修改了,操作系统可能会把它取为页大小的整数倍。如果线程的栈指针溢出到警戒区域,应用程序就可能通过信号接收到出错信息。
Single UNIX Specification还定义了一些其他的可选线程属性供实时应用程序使用,但在这里不讨论这些属性。
线程还有一些其他的pthread_attr_t
结构中没有表示的属性:可撤销状态和可撤销类型。我们将在12.7节中讨论它们。