一、属性对象
通过设置属性,可以指定一种不同于缺省行为的行为。使用 pthread_create创建线程时,或初始化同步变量时,可以指定属性对象。缺省值通常是可以满足需求的。
属性对象是不透明的,因而不能通过赋值直接进行修改。相应的系统提供了一组函数,用于初始化、配置和销毁每种对象类型。
初始化和配置属性后,属性便具有进程范围的作用域。使用属性时最好的方法即是在程序执行早期一次配置好所有必需的状态规范。然后,根据需要引用相应的属性对象。
使用属性对象具有两个主要优点:
1 使用属性对象可增加代码可移植性
即使支持的属性可能会在实现之间有所变化,也只需要管理属性对象即可,而不许要修改线程代码的其它部分。
2 应用程序中的状态规范已被简化
例如,假设进程中可能存在多组线程。每组线程都提供单独的服务。每组线程都有各自的状态要求。
在应用程序执行初期的某一时间,可以针对每组线程初始化线程属性对象。以后所有线程的创建都会引用已经为这类线程初始化的属性对象。初始化阶段是简单和局部的。将来可以快速且可靠地进行任何修改。
二、API
1.初始化/销毁属性
- #include <pthread.h>
- int pthread_attr_init(pthread_attr_t *tattr); 调用成功完成后将返回零,其它返回值表示出错
该函数将属性对象初始化为其缺省值。存储空间是在执行期间由线程系统分配的。缺省的属性及其值如下:
- scope PTHREAD_SCOPE_PROCESS 新线程与进程中的其他线程发生竞争。
- detachstate PTHREAD_CREATE_JOINABLE 线程退出后,保留完成状态和线程ID。
- stackaddr NULL 新线程具有系统分配的栈地址。
- stacksize 0 新线程具有系统定义的栈大小。
- priority 0 新线程的优先级为 0。
- inheritsched PTHREAD_EXPLICIT_SCHED 新线程不继承父线程调度优先级。
- schedpolicy SCHED_OTHER 新线程对同步对象争用使用的调度策略。
- #include <pthread.h>
- int pthread_attr_destroy(pthread_attr_t *tattr); 调用成功完成后将返回零,其它返回值表示出错
该函数用于删除初始化期间分配的存储空间。属性对象将会无效。
2.设置/获取分离状态
如果创建分离线程 (PTHREAD_CREATE_DETACHED),则该线程一退出,便可重用其线程 ID 和其他资源。如果调用线程不准备等待线程退出,可以使用 pthread_attr_setdetachstate。
- #include <pthread.h>
- int pthread_attr_setdetachstate(pthread_attr_t *tattr,int detachstate); 调用成功完成后将返回零,其它返回值表示出错
如果使用 PTHREAD_CREATE_JOINABLE 创建非分离线程,则假设应用程序将等待线程完成。也就是说,程序将对线程执行 pthread_join()。无论是创建分离线程还是非分离线程,在所有线程都退出之前,进程不会退出。
非分离线程在终止后,必须要有一个线程用join 来等待它。否则,不会释放该线程的资源以供新线程使用,而这通常会导致内存泄漏。因此,如果不希望线程被等待,请将该线程作为分离线程来创建。
- #include <pthread.h>
- int pthread_attr_getdetachstate(const pthread_attr_t *tattr, int *detachstate); 调用成功完成后将返回零,其它返回值表示出错
该函数可以获取线程的分离状态
3. 设置/获取栈溢出保护区大小
- #include <pthread.h>
- int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
该函数可以设置attr对象的 guardsize。
出于以下两个原因,为应用程序提供了 guardsize 属性:
- 溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。
- 线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。
guardsize 参数提供了对栈指针溢出的保护。如果创建线程的栈时使用了保护功能,则实现会在栈的溢出端分配额外内存。此额外内存的作用与缓冲区一样,可以防止栈指针的栈溢出。如果应用程序溢出到此缓冲区中,这个错误可能会导致 SIGSEGV 信号被发送给该线程。
如果 guardsize 为零,则不会为使用 attr 创建的线程提供溢出保护区。如果 guardsize 大于零,则会为每个使用 attr 创建的线程提供大小至少为 guardsize 字节的溢出保护区。缺省情况下,线程具有实现定义的非零溢出保护区。
- #include <pthread.h>
- int pthread_attr_getguardsize(const pthread_attr_t *attr,size_t *guardsize);
该函数用于获取 attr 对象的 guardsize。
具体的实现可能将guardsize中包含的值向上舍入为可配置系统变量 PAGESIZE 的倍数。
4.设置/获取争用范围
- #include <pthread.h>
- int pthread_attr_setscope(pthread_attr_t *tattr,int scope); 调用成功完成后将返回零,其它返回值表示出错
该函数用于设置线程的争用范围(PTHREAD_SCOPE_SYSTEM 或PTHREAD_SCOPE_PROCESS)。 使用 PTHREAD_SCOPE_SYSTEM 时,此线程将与系统中的所有线程进行竞争。使用 PTHREAD_SCOPE_PROCESS 时,此线程将与进程中的其他线程进行竞争。
- #include <pthread.h>
- int pthread_attr_getscope(pthread_attr_t *tattr, int *scope); 调用成功完成后将返回零,其它返回值表示出错
该函数用于获取线程的争用范围。
5. 设置/获取线程并行级别
- #include <pthread.h>
- int pthread_setconcurrency(int new_level);
该函数用于通知系统其所需的并发级别。
- #include <pthread.h>
- int pthread_getconcurrency(void);
该函数用于获取先前通过pthread_setconcurrency设置的并行级别,如果以前未调用 pthread_setconcurrency() 函数,则 pthread_getconcurrency() 将返回零。
6.设置/获取调度策略
- #include <pthread.h>
- int pthread_attr_setschedpolicy(pthread_attr_t *tattr, int policy); 调用成功完成后将返回零,其它返回值表示出错
该函数用于设置线程的调度策略。POSIX 标准定义了SCHED_FIFO(先入先出)、SCHED_RR(循环)或 SCHED_OTHER(实现定义的方法)的调度策略属性。
- #include <pthread.h>
- int pthread_attr_getschedpolicy(pthread_attr_t *tattr, int *policy); 调用成功完成后将返回零,其它返回值表示出错
该函数用于获取线程的调度策略
7.设置/获取继承的调度策略
- #include <pthread.h>
- int pthread_attr_setinheritsched(pthread_attr_t *tattr, int inherit); 调用成功完成后将返回零,其它返回值表示出错
该函数用于设置继承的调度策略。
inherit 值 PTHREAD_INHERIT_SCHED 表示新建的线程将继承创建者线程中定义的调度策略。将忽略在 pthread_create() 调用中定义的所有调度属性。如果使用缺省值PTHREAD_EXPLICIT_SCHED,则将使用 pthread_create() 调用中的属性。
- #include <pthread.h>
- int pthread_attr_getinheritsched(pthread_attr_t *tattr, int *inherit);
该函数用于获取由 pthread_attr_setinheritsched() 设置的继承的调度策略。
8.设置/获取调度参数
- #include <pthread.h>
- int pthread_attr_setschedparam(pthread_attr_t *tattr, const struct sched_param *param); 调用成功完成后将返回零,其它返回值表示出错
该函数用于设置线程的调度参数。调度参数是在 param 结构中定义的。仅支持优先级参数。新创建的线程使用此优先级运行。
可以采用两种方式之一来管理 pthreads 优先级:
- 创建子线程之前,可以设置要创建的子线程的优先级属性
- 更改父线程的优先级,子线程创建完成后再将该优先级改回来,子线程将继承父线程的优先级
- #include <pthread.h>
- int pthread_attr_getschedparam(pthread_attr_t *tattr, const struct sched_param *param); 调用成功完成后将返回零,其它返回值表示出错
该函数用于获取由 pthread_attr_setschedparam() 定义的调度参数。
9.设置/获取栈大小
- #include <pthread.h>
- int pthread_attr_setstacksize(pthread_attr_t *tattr, size_t size); 调用成功完成后将返回零,其它返回值表示出错
该函数用于设置线程的栈大小。
stacksize 属性定义系统分配的栈大小(以字节为单位)。size 不应小于系统定义的最小栈大小。size 包含新线程使用的栈的字节数。如果 size 为零,则使用缺省大小。在大多数情况下,零值最适合。
PTHREAD_STACK_MIN 是启动线程所需的栈空间量。此栈空间没有考虑执行应用程序代码所需的空间要求。
- #include <pthread.h>
- int pthread_attr_getstacksize(pthread_attr_t *tattr, size_t *size); 调用成功完成后将返回零,其它返回值表示出
该函数用于获取由 pthread_attr_setstacksize() 设置的栈大小。
关于栈:
通常,线程栈是从页边界开始的。任何指定的大小都被向上舍入到下一个页边界。不具备访问权限的页将被附加到栈的溢出端。大多数栈溢出都会导致将 SIGSEGV 信号发送到违例线程。
指定栈时,还应使用 PTHREAD_CREATE_JOINABLE 创建线程。在该线程的 pthread_join 调用返回之前,不会释放该栈。在该线程终止之前,不会释放该线程的栈。了解这类线程是否已终止的唯一可靠方式是使用pthread_join
为线程分配栈空间:
一般情况下,不需要为线程分配栈空间。系统会为每个线程的栈分配 合适大小的虚拟内存,而不保留任何交换空间。系统将使用 mmap()的 MAP_NORESERVE 选项来进行分配。
系统创建的每个线程栈都具有红色区域。系统通过将页附加到栈的溢出端来创建红色区域,从而捕获栈溢出。此类页无效,而且会导致内存(访问时)故障。红色区域将被附加到所有自动分配的栈,无论大小是由应用程序指定,还是使用缺省大小。
需要注意的是对于库调用和动态链接,运行时栈要求有所变化。应绝对确定指定的栈满足库调用和动态链接的运行时要求。
极少数情况下需要指定栈和/或栈大小。栈大小取决于执行中特定运行时环境的需要。因而很难为为其设定大小。
生成自己的栈:
指定线程栈大小时,必须考虑被调用函数以及每个要调用的后续函数的分配需求。需要考虑的因素应包括调用序列需求、局部变量和信息结构。
有时,您需要与缺省栈略有不同的栈。典型的情况是,线程需要的栈大小大于缺省栈大小。而不太典型的情况是,缺省大小太大。您可能正在使用不足的虚拟内存创建数千个线程,进而处理数千个缺省线程栈所需的数千兆字节的栈空间。
对栈的最大大小的限制通常较为明显,但对其最小大小的限制如何呢?必须存在足够的栈空间来处理推入栈的所有栈帧,及其局部变量等。
宏 PTHREAD_STACK_MIN定义了最小栈的大小。
10.设置/获取栈地址和大小
- #include <pthread.h>
- int pthread_attr_setstack(pthread_attr_t *tattr,void *stackaddr, size_t stacksize); 调用成功完成后将返回零,其它返回值表示出错
该函数可以设置线程栈地址和大小。
stackaddr 属性定义线程栈的基准(低位地址)。stacksize 属性指定栈的大小。如果将stackaddr设置为非空值,而不是缺省的 NULL,则系统将在该地址初始化栈,假设大小为stacksize。
- #include <pthread.h>
- int pthread_attr_getstack(pthread_attr_t *tattr,void * *stackaddr, size_t *stacksize); 调用成功完成后将返回零,其它返回值表示出错
该函数返回由 pthread_attr_setstack() 设置的线程栈地址和大小。