背景
上周在处理一个问题,涉及到了线程调度策略的问题。因为我们要求线程的属性默认为实时策略(SCHED_RR),但是通过proc文件系统查看,实际生效的是分时调度策略(SCHED_OTHER)。通过分析,发现是由于创建线程是设置的参数并不正确,导致的问题。也顺便复习和了解了一下线程创建属性的知识点。在这里做一个记录,当做是以后的复习知识点之一。
我们知道创建线程的API为
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
其中const pthread_attr_t *restrict_attr
参数是可以设置线程属性的,但我们平时一般传入NULL,为默认属性,比如调度策略为分时调度,优先级为0等默认参数。但事实证明这个参数的重要性,我们接下来会好好分析一下这个参数的作用:
如果我们想要使用该参数,要遵循以下步骤:
- pthread_attr_init:初始化
- pthread_attr_destory:去初始化
pthread_attr_init之后,参数里面的值都是操作系统中默认值了。
线程的属性结构如下:
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_attr_t变量进行赋值,必须通过提供的特定接口去设置其中的属性。这也实现了跨平台的功能。下面开始重点讲这里面参数的意义!!!
线程的分离状态
detachstate决定一个线程以什么样的方式来终止自己,默认情况下线程是非分离状态的;
首先,我们知道,创建一个线程之后,(线程默认是非分离的)。我们需要pthread_join等待线程结束,再进行回收,否则会造成内存泄漏或僵尸线程等。但是当pthread_join被调用之后,调用线程就会被阻塞,不能进行下面的操作,这也是非常麻烦的一件事。这个时候分离就能给我提供了这样的机会,创建一个分离的线程,主线程就不需要等待它结束在释放资源,被创建的线程结束后,会自己释放相关资源。使用方式如下:
#include<pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t * attr,int * detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *sttr,int detachstate);
参数:
attr:线程属性变量
detachstate:分离状态属性
PTHREAD_CREATE_DETACHED 分离状态启动
PTHREAD_CREATE_JOINABLE 正常启动线程
成功返回0,失败返回-1
线程的继承性
inheritsched决定子线程的调度策略和调度参数使用的是attr参数里面的,还是默认继承父线程的。我们设置实时策略(SCHED_RR)不成功就是因为这个原因。由于默认是继承父线程的属性,而父线程的属性默认为分时调度。导致设置失败。使用方式如下:
#include <pthread.h>
int pthread_attr_getinheritsched(const pthread_attr_t * attr,int * inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t * attr,int inheritsched);
参数:
attr :线程参数属性
inheritsched:线程的继承性
PTHREAD_INHERIT_SCHED 新的线程继承创建线程的策略和调度参数
PTHREAD_EXPLICIT_SCHED 新的线程使用attr中的调度策略和调度参数
成功返回0,失败返回-1
调度策略
schedpolicy决定线程将以什么调度策略被调度。linux下的调度策略有三种:
SCHED_OTHER 分时调度
SCHED_FIFO 实时调度(先进先出)
SCHED_RR 实时调度(公平轮转法)
关于三者之间的区别,有兴趣的朋友可以看我相应的文章。使用方式如下:
#include <pthread.h>
int pthread_attr_getschedpolicy(const pthread_attr_t * attr, int * policy);
int pthread_attrsetschedpolicy(pthread_attr_t * attr, int plicy);
参数:
attr :线程参数属性
policy:调度策略
SCHED_OTHER 分时调
SCHED_FIFO 实时调度(先进先出)
SCHED_RR 实时调度(公平轮转法)
成功返回0,失败返回-1
调度参数
schedparam决定调度策略中的一些参数选择,目前一般仅支持设备优先级。在头文件中的定义如下:
struct sched_param
{
int sched_priority;参数的本质就是优先级
}
使用方式如下:
#include <pthread.h>
int pthread_attr_getschedparam(const pthread_attr_t * attr, sched_param* param);
int pthread_attr_setschedparam(pthread_attr_t * attr,sched_param * param);
参数:
attr :线程参数属性
param: sched_param结构体
成功返回0,失败返回-1.
注意:系统提供了获取最大优先级和最小优先级的接口
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);
参数:
policy:调度策略,SCHED_OTHER,SCHED_FIFO,SCHED_RR
return 对应调度策略最大或最小的优先级
注意:实时的调度策略的优先级范围是1~99.
分时的调度策略的优先级范围是0
线程的作用域
scope决定线程的作用域,也就是说和哪些线程竞争资源。可以设置为同进程内竞争资源,也可以设置为系统内竞争资源。如果是同进程内竞争资源,当正在运行的线程优先级不高但不是同进程里面的线程,而该线程的优先级较高并且是实时策略。也是无法进行抢占资源的。反之如果是系统内竞争资源,就可以抢占。使用方式如下:
#include<pthread.h>
int pthread_attr_getscope(const pthread_attr_t * attr,int * scope);
int pthread_attr_setscope(pthread_attr_t * attr,int * scope);
参数:
attr :线程参数属性
scope:线程的作用域
PTHREAD_SCOPE_PROCESS 进程内竞争资源
PTHREAD_SCOPE_SYSTEM 系统级竞争资源
线程堆栈大小
stacksize决定线程栈的大小。因为我们默认线程创建之后,线程栈的大小,默认为8M,这对于内存比较紧张的嵌入式平台而言是巨大的浪费。所以我们需要对线程的业务需求设置合适的栈空间大小。因为当我们的局部变量占用空间超过栈空间,就会引起coredump。栈最小为16kb。使用如下:
#include<pthread.h>
int pthread_attr_getstacksize(const pthread_attr_t * attr,size_t * stacksize);
int pthread_attr_setsstacksize( pthread_attr_t * attr,size_t * stacksize);
参数:
attr :线程参数属性
stacksize:堆栈大小
成功返回0,失败返回-1.
线程堆栈地址
stackaddr决定栈空间的地址,具体用在什么方面,我也不清楚,目前知道需要注意的是:设定的栈地址必须以linux页面大小对齐。使用如下:
#include<pthread.h>
int pthread_attr_getstack(const pthread_attr_t * attr,void * stackaddr);
int pthread_attr_setstackaddr(pthread_attr_t * attr,void * stackaddr);
参数:
attr :线程参数属性
stackaddr:栈地址
成功返回0,失败返回-1.
警戒缓冲区
guardsize决定线程栈末尾之后以避免栈溢出的扩展内存大小。我们知道当我们局部变量较多占用空间超过栈空间大小时,就会发生coredump。而该值是保证超过多少以内不会抛出异常的保护,相当于扩容。使用如下:
#include<pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t * attr,size_t*guardsize);
int pthread_attr_setguardsize(pthread_attr_t * attr,size_t * guardsize);
参数:
attr :线程参数属性
guardsize:警戒缓冲区的大小
成功返回0,失败返回-1.
若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途