您的位置:首页 > 运维架构 > Linux

Linux多线程基础学习(六)线程属性

2015-10-20 13:23 441 查看

线程属性

//线程属性结构如下: 

typedef struct
{
int                     detachstate;  //线程的分离状态
int                     schedpolicy;  //线程调度策略
struct sched_param      schedparam;   //线程的调度参数
int                     inheritsched; //线程的继承性
int                     scope;        //线程的作用域
size_t                  guardsize;    //线程栈末尾的警戒缓冲区大小
int                     stackaddr_set;
void *                  stackaddr;    //线程栈的位置
size_t                  stacksize;    //线程栈的大小
}pthread_attr_t;


然而我看到pthread.h中的定义是这样的,并没有看到具体的成员,只是分配了对应的存储空间

typedef union
{
char __size[__SIZEOF_PTHREAD_ATTR_T];
long int __align;
} pthread_attr_t;


原因,应该是Linux不希望用户在新建用户线程的时候可以直接访问线程属性的数据成员,因为可能用户在这里设置了未定义的数值导致线程奔溃,用户只能通过调用Linux提供的结构体的初始化函数对其进行变量初始化。这样做的好处在下文中会说明。

通过设置属性,可以指定一种不同于缺省行为的行为。使用pthread_create()创建线程时或初始化同步变量时,可以指定属性对象。一般情况下pthread_create属性参数缺省值通常就足够了。

属性对象是不透明的,并不能通过赋值直接进行修改。系统提供了用于初始化、配置和销毁每种对象类型。

初始化和配置属性后,属性便具有进程范围的作用域。使用属性时最好的方法即是在程序执行早期一次配置好所有必需的状态规范。然后,根据需要引用相应的属性对象。

使用属性对象具有两个主要优点。

■ 使用属性对象可增加代码可移植性。

即使支持的属性可能会在实现之间有所变化,但您不需要修改用于创建线程实体的函数

调用。这些函数调用不需要进行修改,因为属性对象是隐藏在接口之后的。

如果目标系统支持的属性在当前系统中不存在,则必须显式提供才能管理新的属性。管

理这些属性是一项非常容易的移植任务,因为只需在明确定义的位置初始化属性对象一

次即可。

■ 应用程序中的状态规范已被简化。

例如,假设进程中可能存在多组线程。每组线程都提供单独的服务。每组线程都有各自

的状态要求。在应用程序执行初期的某一时间,可以针对每组线程初始化线程属性对象。以后所有线程的创建都会引用已经为这类线程初始化的属性对象。初始化阶段是简单和局部的。将来就可以快速且可靠地进行任何修改。

1 初始化属性

pthread_attr_init()将对象属性初始化为其缺省值。存储空间是在执行期间由线程系统分配的。

函数原型

int pthread_attr_init(pthread_attr_t *tattr);


#include <pthread.h>
pthread_attr_t tattr;
int ret;
/* initialize an attribute to the default value */
ret = pthread_attr_init(&tattr);




2 销毁属性

函数原型

int pthread_attr_destroy(pthread_attr_t *tattr);


#include <pthread.h>
pthread_attr_t tattr;
int ret;
/* destroy an attribute */
ret = pthread_attr_destroy(&tattr);


3 设置分离状态

如果创建分离线程(PTHREAD_CREATE_DETACHED),则该线程一退出,便可重用其线程ID和其他资源。如果调用线程不准备等待线程退出

函数原型

int pthread_attr_setdetachstate(pthread_attr_t *tattr,int detachstate);

#include <pthread.h>
pthread_attr_t tattr;
int ret;
/* set the thread detach state */
ret = pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_DETACHED);


如果使用PTHREAD_CREATE_JOINABLE创建非分离线程,则假设应用程序将等待线程完成。也就是说,程序将对线程执行pthread_join()。

非分离线程在终止后,必须要有一个线程用join来等待它。否则,不会释放该线程的资源以供新线程使用,而这通常会导致内存泄漏。因此,如果不希望线程被等待,请将该线程作为分离线程来创建。

4 设置栈溢出保护区大小

pthread_attr_setguardsize()可以设置attr对象的guardsize。

函数原型

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

出于以下两个原因,为应用程序提供了guardsize属性:

■ 溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。

■ 线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。

guardsize参数提供了对栈指针溢出的保护。如果创建线
4000
程的栈时使用了保护功能,则实现会在栈的溢出端分配额外内存。此额外内存的作用与缓冲区一样,可以防止栈指针的栈溢出。如果应用程序溢出到此缓冲区中,这个错误可能会导致SIGSEGV信号被发送给该线程。如果guardsize为零,则不会为使用attr创建的线程提供溢出保护区。如果guardsize大于零,则会为每个使用attr创建的线程提供大小至少为guardsize字节的溢出保护区。缺省情况下,线程具有实现定义的非零溢出保护区。

允许合乎惯例的实现,将guardsize的值向上舍入为可配置的系统变量PAGESIZE的倍数。请参见sys/mman.h中的PAGESIZE。如果实现将guardsize的值向上舍入为PAGESIZE的倍数,则以guardsize(先前调用pthread_attr_setguardsize()时指定的溢出保护区大小)为单位存储对指定attr的pthread_attr_getguardsize()的调用。

 

5 设置竞争范围

请使用pthread_attr_setscope()建立线程的争用范围(PTHREAD_SCOPE_SYSTEM或PTHREAD_SCOPE_PROCESS)。 使用PTHREAD_SCOPE_SYSTEM时,此线程将与系统中的所有线程进行竞争。使用PTHREAD_SCOPE_PROCESS时,此线程将与进程中的其他线程进行竞争。

函数原型:

int pthread_attr_setscope(pthread_attr_t *tattr,int scope);

#include <pthread.h>
pthread_attr_t tattr;
int ret;
/* bound thread */
ret = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM);
/* unbound thread */
ret = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_PROCESS);


6 设置线程并行级别

pthread_setconcurrency()通知系统其所需的并发级别。

函数原型:

int pthread_setconcurrency(int new_level);


7 设置调度策略

pthread_attr_setschedpolicy()设置调度策略。POSIX标准指定SCHED_FIFO(先入先出)、SCHED_RR(循环)或SCHED_OTHER(实现定义的方法)的调度策略属性

函数原型:

int pthread_attr_setschedpolicy(pthread_attr_t *tattr, int policy);

#include <pthread.h>
pthread_attr_t tattr;
int policy;
int ret;
/* set the scheduling policy to SCHED_OTHER */
ret = pthread_attr_setschedpolicy(&tattr, SCHED_OTHER);


■ SCHED_FIFO

如果调用进程具有有效的用户ID0,则争用范围为系统(PTHREAD_SCOPE_SYSTEM)的先入

先出线程属于实时(RT)调度类。如果这些线程未被优先级更高的线程抢占,则会继续处

理该线程,直到该线程放弃或阻塞为止。对于具有进程争用范围

(PTHREAD_SCOPE_PROCESS))的线程或其调用进程没有有效用户ID 0的线程,请使用

SCHED_FIFO。SCHED_FIFO基于TS调度类。

■ SCHED_RR

如果调用进程具有有效的用户ID0,则争用范围为系统(PTHREAD_SCOPE_SYSTEM))的循环

线程属于实时(RT)调度类。如果这些线程未被优先级更高的线程抢占,并且这些线程没

有放弃或阻塞,则在系统确定的时间段内将一直执行这些线程。对于具有进程争用范围

(PTHREAD_SCOPE_PROCESS)的线程,请使用SCHED_RR(基于TS调度类)。此外,这些线

程的调用进程没有有效的用户ID0。

SCHED_FIFO和SCHED_RR在POSIX标准中是可选的,而且仅用于实时线程。

8 设置继承的调度策略

函数原型:

int pthread_attr_setinheritsched(pthread_attr_t *tattr, int inherit);

#include <pthread.h>
pthread_attr_t tattr;
int inherit;
int ret;
/* use the current scheduling policy */
ret = pthread_attr_setinheritsched(&tattr, PTHREAD_EXPLICIT_SCHED);


inherit值如果是PTHREAD_INHERIT_SCHED表示新建的线程将继承创建者线程中定义的调度策略。将忽略在pthread_create()调用中定义的所有调度属性。

如果使用缺省值PTHREAD_EXPLICIT_SCHED,则将使用pthread_create()调用中的属性。

 

9 设置调度参数

函数原型:

int pthread_attr_setschedparam(pthread_attr_t *tattr, const struct sched_param *param);

#include <pthread.h>
pthread_attr_t tattr;
int newprio;
sched_param param;
newprio= 30;
/* set the priority; others are unchanged */
param.sched_priority = newprio;
/* set the new scheduling param */
ret = pthread_attr_setschedparam (&tattr,¶m);


调度参数是在param结构中定义的。仅支持优先级参数。新创建的线程使用此优先级运行。

 

使用指定的优先级创建线程

创建线程之前,可以设置优先级属性。将使用在sched_param结构中指定的新优先级创建子

线程。此结构还包含其他调度信息。

创建子线程时建议执行以下操作:

■ 获取现有参数

■ 更改优先级

■ 创建子线程

■ 恢复原始优先级

 

10 设置栈大小

函数原型

int pthread_attr_setstacksize(pthread_attr_t *tattr,size_t size);


#include <pthread.h>
pthread_attr_t tattr;
size_t size;
int ret;
size = (PTHREAD_STACK_MIN + 0x4000);
/* setting a new size */
ret = pthread_attr_setstacksize(&tattr,size);


stacksize属性定义系统分配的栈大小(以字节为单位)。size不应小于系统定义的最小栈大小。

size包含新线程使用的栈的字节数。如果size为零,则使用缺省大小。

PTHREAD_STACK_MIN是启动线程所需的栈空间量。此栈空间没有考虑执行应用程序代码所需的线程例程要求。

通常,线程栈是从页边界开始的。任何指定的大小都被向上舍入到下一个页边界。不具备

访问权限的页将被附加到栈的溢出端。大多数栈溢出都会导致将SIGSEGV信号发送到违例线

程。将直接使用调用方分配的线程栈,而不进行修改。

指定栈时,还应使用PTHREAD_CREATE_JOINABLE创建线程。在该线程的pthread_join(3C)调

用返回之前,不会释放该栈。在该线程终止之前,不会释放该线程的栈。了解这类线程是

否已终止的唯一可靠方式是使用pthread_join(3C)。

为线程分配栈空间

一般情况下,不需要为线程分配栈空间。系统会为每个线程的栈分配1MB(对于32位系统)或2MB(对于64位系统)的虚拟内存,而不保留任何交换空间。系统将使用mmap()的MAP_NORESERVE选项来进行分配。

系统创建的每个线程栈都具有红色区域。系统通过将页附加到栈的溢出端来创建红色区域,从而捕获栈溢出。此类页无效,而且会导致内存(访问时)故障。红色区域将被附加到所有自动分配的栈,无论大小是由应用程序指定,还是使用缺省大小。

极少数情况下需要指定栈和/或栈大小。程序员很难了解是否指定了正确的大小。甚至符合ABI标准的程序也不能静态确定其栈大小。栈大小取决于执行中特定运行时环境。

生成自己的栈

指定线程栈大小时,必须考虑被调用函数以及每个要调用的后续函数的分配需求。需要考虑的因素应包括调用序列需求、局部变量和信息结构。

有时,需要与缺省栈不同的栈。一般的情况是,线程需要的栈大小大于缺省栈大小。少数情况,缺省大小太大。因为可能正在使用不足的虚拟内存创建大量线程,进而处理这些个缺省线程栈所需的大量兆字节的栈空间。

对栈的最大大小的限制通常较为明显,但对其最小大小的限制如何呢?必须存在足够的栈空间来处理推入栈的所有栈帧,及其局部变量等。

要获取对栈大小的绝对最小限制,请调用宏PTHREAD_STACK_MIN。PTHREAD_STACK_MIN宏将针对执行NULL过程的线程返回所需的栈空间量。有用的线程所需的栈大小大于最小栈大小,因此缩小栈大小时应非常谨慎。

#include <pthread.h>
pthread_attr_t tattr;
pthread_t tid;
int ret;
size_t size = PTHREAD_STACK_MIN + 0x4000;
ret = pthread_attr_init(&tattr); /* initialized with default attributes */

ret = pthread_attr_setstacksize(&tattr,size); /* setting the size of the stack also */

ret = pthread_create(&tid,&tattr,start_routine,arg); /* only size specified in tattr*/


</pre><pre>

11 设置栈地址和大小

pthread_attr_setstack()可以设置线程栈地址和大小。

函数原型:

int pthread_attr_setstack(pthread_attr_t *tattr,void *stackaddr,size_t stacksize);

#include <pthread.h>
pthread_attr_t tattr;
void *base;
size_t size;
int ret;
base= (void *) malloc(PTHREAD_STACK_MIN + 0x4000);
/* setting a new address and size */
ret = pthread_attr_setstack(&tattr,base,PTHREAD_STACK_MIN + 0x4000);


stackaddr属性定义线程栈的基准(低位地址)。stacksize属性指定栈的大小。如果将stackaddr设置为非空值,而不是缺省的NULL,则系统将在该地址初始化栈,假设大小为stacksize。

base包含新线程使用的栈的地址。如果base为NULL,则pthread_create(3C)将为大小至少为stacksize字节的新线程分配栈。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux 多线程