线程属性

    在 线程基础函数一节中我们曾提到在调用 pthread_create 函数时可以指定线程属性,还可以用 pthread_detach 函数来分离线程,以让操作系统在线程退出时收回它所占用的资源。现在就是深入讨论这个话题的时候。
    可以使用 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_init 函数可以用来初始化 pthread_attr_t 结构。调用该函数后,线程属性结构包含的就是操作系统支持的所有线程属性的默认值。pthread_attr_destroy 函数则可以释放动态分配的属性对象的内存空间,还会用无效的值初始化属性对象。
    下表总结了 POSIX.1 定义的线程属性以及各个操作系统平台的支持情况。

    如果在创建线程时就知道不需要了解线程的终止状态,就可以使用 pthread_attr_t 结构中的 detachstate 线程属性,让线程一开始就处于分离状态。pthread_attr_setdetachstate 函数可以把线程属性设置成以下两个合法值之一:PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者 PTHREAD_CREATE_JOINABLE,正常启动线程,此时应用程序可以获取线程的终止状态。函数 pthread_attr_getdetachstate 则可用来获取当前的 detachstate 线程属性。
#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;否则,返回错误编号 */

    下面给出了一个以分离状态创建线程的函数。
#include <pthread.h>

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 <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);

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 <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
                         /* 返回值:若成功,返回 0;否则,返回错误编号 */
void pthread_testcancel(void);

    pthread_setcancelstate 把当前的可取消状态设置为 state,并把原来的可取消状态存储在 oldstate 指向的内存单元中,这两步是一个原子操作。
    因为 pthread_cancel 不会等待线程终止,所以默认情况下,线程在取消请求发出后会继续运行,直到线程到达某个取消点。取消点是线程检查它是否被取消的一个位置,如果取消了,则按照请求行事。POSIX.1 保证取消点在线程调用下图中的任何函数时都会出现。

    线程默认是可取消的,当可取消状态设为 PTHREAD_CANCEL_DISABLE 时,调用 pthread_cancel 并不会杀死线程。相反,取消请求对这个线程来说还处于挂起状态。当取消状态再次变为 PTHREAD_CANCEL_ENABLE 时,线程将在下一个取消点上对所有挂起的取消请求进行处理。
    除了上图中的函数,POSIX.1 还指定了下图中的函数作为可选的取消点。

    如果应用程序在很长的一段时间内都不会调用上面两个图中的函数,也可以调用 pthread_testcancel 函数在程序中添加自己的取消点。调用该函数时,如果有某个取消请求正处于挂起状态,而且取消并没有置为无效,那么线程就会被取消,否则将不会有任何效果。
    上面描述的默认的取消类型也称为推迟取消,调用 pthread_cancel 后,在线程到达取消点之前,并不会出现真正的取消。可以通过 pthread_setcanceltype 来修改取消类型。
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
                         /* 返回值:若成功,返回 0;否则,返回错误编号 */

    type 参数可以是 PTHREAD_CANCEL_DEFERRED 或 PTHREAD_CANCEL_ASYNCHRONOUS,该函数把取消类型设置为 type,并把原来的取消类型保存到 oldtype 指向的内存单元中。使用异步取消时,线程就可以在任意时间撤销了。

猜你喜欢

转载自aisxyz.iteye.com/blog/2399739