线程属性
Linux操作系统中,在创建线程时通常采用的都是默认属性,使用默认属性创建的线程已经可以解决绝大部分开发时遇到的问题,如果需要在某些方面对程序的性能提出更高的要求,那么就需要我们对线程的属性进行修改,例如:修改线程使用的内存大小,以此达到在线程中存储更多数据或降低使用内存的功能。
Detach state [线程的分离状态]: 线程将使用什么样方式回收自己的资源
Scope [线程的作用域]: 线程将与哪些线程竞争CPU资源
Inherit scheduler [线程的继承策略]: 线程是否继承父线程的调度策略
Scheduling policy [线程的调度策略]: 线程使用实时调度策略或非实时调度策略
Scheduling priority [线程的调度优先级]: 线程优先被响应的级别(只在实时调度策略下生效)
Guard size [线程的警戒缓冲区大小]: 线程栈溢出时用于保护其他数据的一块隔断内存的大小
Stack address [线程使用的栈地址]: 线程使用的内存起始地址
Stack size [线程使用的栈大小]: 线程使用的内存大小
1、线程的分离状态
线程中有一个名为Detach state的属性,叫做“分离状态”,该属性决定线程的行为结束后将采取什么样的方式释放自己所占用的资源。
默认属性中的分离状态为PTHREAD_CREATE_JOINABLE,即“结合状态”,此状态下的线程结束后不会主动释放资源,而是需要调用pthread_join函数来回收资源;如果将分离状态值调整为PTHREAD_CREATE_DETACHED,则为“分离状态”,此状态下的线程结束后能够自动释放自己的资源。
// 获取分离状态属性值
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
// 修改分离状态属性值
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
2、线程的作用域
线程中“作用域”属性Scope将决定目标线程将与哪些线程竞争CPU资源。
该属性可以设置为两个值:PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS,分别代表“此线程与系统内所有线程竞争资源”或“此线程仅与所属线程中的其他线程竞争资源”。
// 获取作用域属性值
int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
// 设置作用域属性值
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
3、线程使用的内存
如果想要通过修改线程属性来设置线程使用的内存空间,那么首先需要知道,每一个线程被创建出来的时候,都会有一个相对应的线程栈出现,线程使用的栈内存实际是申请在堆空间的。一旦发生栈溢出(栈溢出必定是上溢),数据就会向上覆盖,会影响甚至破坏到库、栈区等空间的数据,这些数据都是属于用户态中的内存,系统是不会报错的,只有继续溢出到内核中的数据时才会报错,但这时已经为时已晚。因此开发人员设计出“警戒缓冲区”这一属性,警戒缓冲区这块内存是不可读写的,因此线程中的数据上溢到这里时系统就会向线程发出信号并杀死该线程,保护其他数据。线程属性Guard size 将用于设置警戒缓冲区的大小。
// 获取警戒缓冲区属性值
int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize);
// 设置警戒缓冲区属性值
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
而线程使用的栈大小一般情况下是8M,如果需要调整这块内存使用的地址空间或大小,可以分别修改“栈地址”和“栈大小”属性(Stack address / Stack Size)。
// 获取地址和大小属性值
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
// 设置地址和大小属性值
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
// 获取地址属性值
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
// 设置地址属性值
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
// 获取大小属性值
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
// 设置大小属性值
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
4、线程的调度
在Linux中,线程实际是由进程来实现,线程就是轻量级进程,因此在Linux中,线程的调度是按照进程的调度方式来进行调度的,也就是说线程是调度单元。Linux这样实现的线程的好处的之一是:线程调度直接使用进程调度就可以了,没必要再搞一个进程内的线程调度器。在Linux中,调度器是基于线程的调度策略(scheduling policy)和调度优先级(static scheduling priority)来决定那个线程来运行。
线程属性Scheduling policy将决定目标线程以何种调度策略来运行,线程的调度策略大致分为两种:实时调度和非实时调度。对于非实时调度策略,线程属性Scheduling priority(静态调度优先级)的设置是不起作用的,等价于优先级为0;实时调度策略下的优先级范围是1-99,数值越大优先级越高,因此实时调度策略中的线程优先级总是高于非实时调度策略中的线程。所有的调度都是抢占式的,如果一个具有更高优先级的线程可以进入运行状态,那么当前运行的全部线程会被强制放入等待队列。下面介绍几种常见的调度策略。
SCHED_OTHER:分时调度策略,属于非实时调度策略。该策略是默认线程属性中的调度策略,该策略下的静态调度优先级总是为0,对于该策略表中的线程,调度器是基于动态优先级进行调度的(动态优先级的值会随着线程的运行时间进行改变,以确保所有分时调度策略表中的线程公平运行)。
SCHED_FIFO:先入先出调度策略,属于实时调度策略,该策略下的线程一旦占用CPU则一直运行,直到有更高优先级任务到达或自己结束。
SCHED_RR:时间片轮询调度,该策略是在SCHED_FIFO的基础上改进而来,为每个线程增加了一个时间片,当时间片用完后,系统将该线程至于队列末尾,保证了SCHED_RR策略表中所有具有相同优先级的线程被公平调度,属于实时调度策略。
// 获取调度策略属性值
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);
// 修改调度策略属性值
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
// 获取静态调度策略优先级值
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
// 设置静态调度策略优先级值
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
5、线程的继承策略
线程属性Inherit scheduler将设置目标线程以怎样的形式获取调度策略和调度优先级。
继承策略的值为PTHREAD_INHERIT_SCHED或PTHREAD_EXPLICIT_SCHED,分别表示从父线程继承或自己进行设置。
// 获取继承策略优先级值
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);
// 设置继承策略优先级值
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);