我们曾提到在调用 pthread_create 函数时可以指定线程属性,还可以用 pthread_detach 函数来分离线程,以让操作系统在线程退出时收回它所占用的资源。现在就是深入讨论这个话题的时候。
可以使用 pthread_attr_t 结构修改线程默认属性,并把这些属性与创建的线程联系起来。
#include
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
/* 两个函数的返回值:若成功,返回 0;否则,返回错误编号 */
pthread_attr_init 函数可以用来初始化 pthread_attr_t 结构。调用该函数后,线程属性结构包含的就是操作系统支持的所有线程属性的默认值。pthread_attr_destroy 函数则可以释放动态分配的属性对象的内存空间,还会用无效的值初始化属性对象。
下表总结了 POSIX.1 定义的线程属性以及各个操作系统平台的支持情况。
[img]http://dl2.iteye.com/upload/attachment/0127/8022/8b080322-9fbc-3d1b-920b-c739e45bd71d.png[/img]
如果在创建线程时就知道不需要了解线程的终止状态,就可以使用 pthread_attr_t 结构中的 detachstate 线程属性,让线程一开始就处于分离状态。pthread_attr_setdetachstate 函数可以把线程属性设置成以下两个合法值之一:PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者 PTHREAD_CREATE_JOINABLE,正常启动线程,此时应用程序可以获取线程的终止状态。函数 pthread_attr_getdetachstate 则可用来获取当前的 detachstate 线程属性。
#include
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int *detachstate);
/* 两个函数的返回值:若成功,返回 0;否则,返回错误编号 */
下面给出了一个以分离状态创建线程的函数。
#include
int makeDetachThread(void *(*fn)(void *), void *arg){
pthread_t tid;
pthread_attr_t attr;
int 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;
}
注意,这里忽略了 pthread_attr_destroy 的返回值,以免覆盖 pthread_create 的返回值。因此万一该函数的清理工作失败,则可能会有少量的内存泄漏。
遵循 POSIX 标准的系统不一定支持线程栈属性,可以在编译阶段使用 _POSIX_THREAD_ATTR_STACKADDR 和 _POSIX_THREAD_ATTR_STACKSIZE 符号或者在运行阶段把 _SC_THREAD_ATTR_STACKADDR 和 _SC_THREAD_ATTR_STACKSIZE 参数传给 sysconf 函数来检查系统是否支持每一个线程栈属性。
下面是一组可用来操作线程栈属性的函数。
#include
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);
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);
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;否则,返回错误编号 */
对于进程来说,虚地址空间的大小是固定的,因为进程中只有一个栈,所以它的大小通常不是问题。但对于线程来说,同样大小的虚地址空间必须被所有的线程栈共享。如果这些线程栈的累计大小超过了可用的虚地址空间,就需要减少默认的线程栈大小。而如果线程调用的函数分配了大量的自动变量,或者涉及许多很深的栈帧,那么所需的栈大小又可能要比默认的大。如果线程栈的虚地址空间都用完了,则可以使用 malloc 或 mmap 来为可替代的栈分配空间,并用 pthread_attr_setstack 函数来改变新建线程的栈位置。其中 stackaddr 参数指定的地址可以用作线程栈的内存范围中的最低可寻址地址(但不一定是栈的开始位置,因为如果栈是从高地址向低地址增长的,则 stackaddr 将是栈的结尾位置),该地址与处理器结构相应的边界应对齐(假设 malloc 和 mmap 所用的虚地址范围与线程栈当前使用的虚地址范围不同)。
pthread_attr_getstacksize 和 pthread_attr_setstacksize 函数可以读取和设置线程属性 stacksize。如果希望改变默认的栈大小,但又不想自己处理线程栈的分配问题,这时使用 pthread_attr_setstacksize 函数就很有用。不过设置 stacksize 属性时,选择的 stacksize 不能小于 PTHREAD_STACK_MIN。
线程属性 guardsize 控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小。该属性默认值是由具体实现来定义的,但常用值是系统页大小。可以把 guardsize 属性设置为 0,这样就不会提供警戒缓冲区。如果修改了 stackaddr 属性,系统也会认为我们将自己管理栈,进而使警戒缓冲区机制无效。如果 guardsize 属性被修改了,操作系统可能会把它取为页大小的整数倍。如果线程的栈指针溢出到警戒区域,应用程序就可能通过信号接收到出错信息。
不过有两个线程属性并没有包含在 pthread_attr_t 结构中,它们是可取消状态和可取消类型。它们影响着线程在响应 pthread_cancel 函数调用时所呈现的行为。
可取消状态属性可以是 PTHREAD_CANCEL_ENABLE 或 PTHREAD_CANCEL_DISABLE。线程可以调用 pthread_setcancelstate 来修改它的可取消状态。
#include
int pthread_setcancelstate(int state, int *oldstate);
/* 返回值:若成功,返回 0;否则,返回错误编号 */
void pthread_testcancel(void);
pthread_setcancelstate 把当前的可取消状态设置为 state,并把原来的可取消状态存储在 oldstate 指向的内存单元中,这两步是一个原子操作。
因为 pthread_cancel 不会等待线程终止,所以默认情况下,线程在取消请求发出后会继续运行,直到线程到达某个取消点。取消点是线程检查它是否被取消的一个位置,如果取消了,则按照请求行事。POSIX.1 保证取消点在线程调用下图中的任何函数时都会出现。
[img]http://dl2.iteye.com/upload/attachment/0128/1002/605bac6f-b0cb-3ad7-80e0-31686de7d87c.png[/img]
线程默认是可取消的,当可取消状态设为 PTHREAD_CANCEL_DISABLE 时,调用 pthread_cancel 并不会杀死线程。相反,取消请求对这个线程来说还处于挂起状态。当取消状态再次变为 PTHREAD_CANCEL_ENABLE 时,线程将在下一个取消点上对所有挂起的取消请求进行处理。
除了上图中的函数,POSIX.1 还指定了下图中的函数作为可选的取消点。
[img]http://dl2.iteye.com/upload/attachment/0128/1004/ed099abc-b69b-31ae-afd6-9f5c15001ce7.png[/img]
如果应用程序在很长的一段时间内都不会调用上面两个图中的函数,也可以调用 pthread_testcancel 函数在程序中添加自己的取消点。调用该函数时,如果有某个取消请求正处于挂起状态,而且取消并没有置为无效,那么线程就会被取消,否则将不会有任何效果。
上面描述的默认的取消类型也称为推迟取消,调用 pthread_cancel 后,在线程到达取消点之前,并不会出现真正的取消。可以通过 pthread_setcanceltype 来修改取消类型。
#include
int pthread_setcanceltype(int type, int *oldtype);
/* 返回值:若成功,返回 0;否则,返回错误编号 */
type 参数可以是 PTHREAD_CANCEL_DEFERRED 或 PTHREAD_CANCEL_ASYNCHRONOUS,该函数把取消类型设置为 type,并把原来的取消类型保存到 oldtype 指向的内存单元中。使用异步取消时,线程就可以在任意时间撤销了。