提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
包括线程的调度函数,遇到的BUG,以及优先级的测试计算等,由于纯靠自己测试以及一些博客的参考,可能存在一些问题。
代码如下(示例):
///
/// @file thread.cc
/// @author fenglang
/// @date 2023-09-01 09:31:52
///
#include
#include
#include
#include
void *thread_function(void *arg) {
// 获取当前线程的线程号
pthread_t thread = pthread_self();
//设置线程优先级相关的参数变量
int policy;
struct sched_param param;
// param.sched_priority = 22;
// 获取当前线程的调度参数
// int result = pthread_setschedparam(thread,SCHED_RR,¶m);//能够设置正在运行的线程,更偏好param参数设置。
// int result = sched_setscheduler(0, SCHED_FIFO, ¶m);//能够设置正在运行的进程,需要和线程调度函数分开
// printf("result = %d \n",result);//获取返回值结果,返回0成功,其他为错误码
// if(result == -1)
// {
// perror("Error");
// }
int rett = pthread_getschedparam(thread, &policy, ¶m);//获取当前线程的调度策略和优先级
if(rett != 0)
{
perror("Error");
}
// while(1)
// {
// sleep(3);
printf("rett = %d , policy = %d , priority = %d ,thread = %lu \n",rett, policy, param.sched_priority,(unsigned long)thread);
// }
return NULL;
}
int main() {
//设置线程属性
pthread_attr_t attr;
pthread_attr_init(&attr);
// 设置线程属性的调度策略为 SCHED_RR
int ret = pthread_attr_setschedpolicy(&attr, SCHED_RR);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);//设置线程属性是否继承父线程属性,必须设置该函数为不继承父线程属性,才能够成功设置子线程属性
int policy;
struct sched_param param;
pthread_t thread;
param.sched_priority = 50;
pthread_attr_setschedparam(&attr,¶m);//设置线程属性的优先级
pthread_create(&thread,&attr , thread_function, NULL);//创建线程
//打印RR策略的最大优先级与最小优先级
// int min = sched_get_priority_min(SCHED_RR);
// int max = sched_get_priority_max(SCHED_RR);
// printf("min_priority = %d, max_priority = %d \n",min,max);
// int result = pthread_getschedparam(thread,&policy,¶m);
// printf("result = %d \n",result);
// if(result != 0)
// {
// perror("Error");
// }
//获取线程属性的策略和优先级参数
pthread_attr_getschedpolicy(&attr, &policy);
pthread_attr_getschedparam(&attr, ¶m);
printf("ret = %d , policy = %d , priority = %d thread = %lu \n ",ret,policy, param.sched_priority,(unsigned long)thread);
pthread_join(thread,NULL);
return 0;
}
关于调度策略设置代码如下(示例):
//1.通过线程属性设置
pthread_attr_t attr;
pthread_attr_init(&attr);
int ret = pthread_attr_setschedpolicy(&attr, SCHED_RR);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
int policy;
struct sched_param param;
param.sched_priority = 50;
pthread_attr_setschedparam(&attr,¶m);//设置线程属性的优先级
.在线程初始化时使用pthread_attr_setschedpolicy函数设置线程调度策略,必须使用pthread_attr_setinheritsched函数设置不继承父线程的属性,否则仅设置属性,创建时默认继承父线程属性,使用该函数修改线程调度策略时,在终端执行程序需要使用sudo用管理员权限执行,不然子线程无法成功执行。
#include
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
inheritsched参数:
线程优先级参数设置
param.sched_priority = 50;
pthread_attr_setschedparam(&attr,¶m);
设置实时调度策略,FIFO和RR必须对param.sched_priority参数进行赋值,取值范围[1,99],否则在后续获取参数线程策略和优先级时会产生参数错误或未知错误。
//1.通过pthread_setschedparam函数设置
pthread_t thread = pthread_self();
int policy;
struct sched_param param;
param.sched_priority = 22;
pthread_setschedparam(thread,SCHED_RR,¶m);
int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param);
设置实时调度策略时,需要对优先级参数param.sched_priority进行设置,否则无法设置调度策略和优先级。同样需要使用管理员权限执行。
//获取线程的调度策略和优先级
pthread_getschedparam(thread, &policy, ¶m);
//获取线程属性的调度策略和优先级
pthread_attr_getschedpolicy(&attr, &policy);
pthread_attr_getschedparam(&attr, ¶m);
1.参数值需要有效,在设置实时调度策略(FIFO和RR)时,需要对参数param赋值,否则使用pthread_getschedparam函数获取参数时会出现未知错误。
2.pthread_attr_getschedparam最好在子线程内部获取线程的调度策略和优先级,在主线程中调用该函数会出现一个奇怪错误,目前尚不知晓原因,如下图所示
int result = pthread_getschedparam(thread,&policy,¶m);
printf("result = %d \n",result);
if(result != 0)
{
perror("Error");
}
实时任务
内核通过下面这个公式来计算真正的优先级数值:
kernel priority = 100 - 1 - param.sched_priority
如果应用层传入数值 0,那么在内核中优先级数值就是 99(100 - 1 - 0 = 99),在所有实时任务中,它的优先级是最低的,和普通任务没啥区别,所以在优先级参数设置的时候无法将实时调度策略的参数设置为0。
如果应用层传输数值 99,那么在内核中优先级数值就是 0(100 - 1 - 99 = 0),在所有实时任务中,它的优先级是最高的。
能够看出,与内核角度相反,所以在我们手动设置参数时,param.sched_priority的值越高,优先级越高
普通任务
调整普通任务的优先级,是通过 nice 值来实现的,内核中也有一个公式来把应用层传入的 nice 值,转成内核角度的优先级数值:
kernel prifoity = 100 + 20 + nice
nice 的合法数值是:-20 ~ 19。
如果应用层设置线程 nice 数值为 -20,那么在内核中优先级数值就是 100(100 + 20 + (-20) = 100),在所有的普通任务中,它的优先级是最高的。
如果应用层设置线程 nice 数值为 19,那么在内核中优先级数值就是 139(100 +20 +19 = 139),在所有的普通任务中,它的优先级是最低的。
能够看出,在设置普通任务的优先级时,nice值越小,优先级就越高
在线程调度策略和优先级测试时,如果是多核CPU,可能多个线程同时执行线程,多线程并发执行,很难看出测试结果,需要设置CPU亲和性,将线程绑定到单一CPU上进行测试,才能够看出测试效果。
硬实时的要求:可重复,决定性时间内做出响应。
个人理解:结果可预期,触发概率100%。
适用场景:机器人控制,传感器采集,汽车安全气囊,紧急制动等
很明显Linux无法做到硬实时需求,无论中断程序的不可嵌套,自旋锁,内存管理机制都决定了Linux无法做到硬实时
并不要求完全在规定时间内100%触发,95%情况能触发就能够接受。
其目的只是根据不同的优先级尽快的完成任务目标。
适用场景:DVD播放,鼠标键盘响应
中断延迟:中断程序一般都是优先级较高的,当中断函数来临时的响应速度可以作为衡量实时性的标准。中断延迟就是从一个外部事件发生到相应的中断处理函数的第一条指令开始执行所需要的时间。
抢占延迟:有时也称调度延迟,系统中运行的任务很多,当优先级高的任务抢占当前任务时所需要的速度也是衡量实时性的标准。抢占延迟就是从一个外部事件发生到相应的处理该事件的任务的第一条命令开始执行的时间。
#include
#include
#include
#include
#include
#include
// 用来打印当前的线程信息:调度策略是什么?优先级是多少?
void get_thread_info(const int thread_index)
{
int policy;
struct sched_param param;
printf("\n====> thread_index = %d \n", thread_index);
pthread_getschedparam(pthread_self(), &policy, ¶m);
if (SCHED_OTHER == policy)
printf("thread_index %d: SCHED_OTHER \n", thread_index);
else if (SCHED_FIFO == policy)
printf("thread_index %d: SCHED_FIFO \n", thread_index);
else if (SCHED_RR == policy)
printf("thread_index %d: SCHED_RR \n", thread_index);
printf("thread_index %d: priority = %d \n", thread_index, param.sched_priority);
}
// 线程函数,
void *thread_routine(void *args)
{
cpu_set_t mask;
//int cpus = sysconf(_SC_NPROCESSORS_CONF);
CPU_ZERO(&mask);
CPU_SET(0, &mask);
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0)
{
printf("set thread affinity failed! \n");
}
//printf("cpus = %d \n",cpus);
// 参数是:线程索引号。四个线程,索引号从 1 到 4,打印信息中使用。
int thread_index = *(int *)args;
// 为了确保所有的线程都创建完毕,让线程睡眠1秒。
sleep(1);
// 打印一下线程相关信息:调度策略、优先级。
get_thread_info(thread_index);
long num = 0;
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5000000; j++)
{
// 没什么意义,纯粹是模拟 CPU 密集计算。
float f1 = ((i+1) * 345.45) * 12.3 * 45.6 / 78.9 / ((j+1) * 4567.89);
float f2 = (i+1) * 12.3 * 45.6 / 78.9 * (j+1);
float f3 = f1 / f2;
}
// 打印计数信息,为了能看到某个线程正在执行
printf("thread_index %d: num = %ld \n", thread_index, num++);
}
// 线程执行结束
printf("thread_index %d: exit \n", thread_index);
return 0;
}
int main(void)
{
// 一共创建四个线程:0和1-实时线程,2和3-普通线程(非实时)
int thread_num = 4;
// 分配的线程索引号,会传递给线程参数
int index[4] = {1, 2, 3, 4};
// 用来保存 4 个线程的 id 号
pthread_t ppid[4];
// 用来设置 2 个实时线程的属性:调度策略和优先级
pthread_attr_t attr[2];
struct sched_param param[2];
int priority = getpriority(PRIO_PROCESS, 0);
printf("Main thread priority: %d\n", priority);
int cpus = sysconf(_SC_NPROCESSORS_CONF);
printf("cpus = %d \n",cpus);
// 实时线程,必须由 root 用户才能创建
if (0 != getuid())
{
printf("Please run as root \n");
exit(0);
}
// 创建 4 个线程
for (int i = 0; i < thread_num; i++)
{
if (i <= 1) // 前2个创建实时线程
{
// 初始化线程属性
pthread_attr_init(&attr[i]);
// 设置调度策略为:SCHED_FIFO
pthread_attr_setschedpolicy(&attr[i], SCHED_FIFO);
// 设置优先级为 51,52。
param[i].__sched_priority = 51 + i;
pthread_attr_setschedparam(&attr[i], ¶m[i]);
// 设置线程属性:不要继承 main 线程的调度策略和优先级。
pthread_attr_setinheritsched(&attr[i], PTHREAD_EXPLICIT_SCHED);
// 创建线程
pthread_create(&ppid[i], &attr[i],thread_routine, (void *)&index[i]);
}
else // 后两个创建普通线程
{
pthread_create(&ppid[i], 0, thread_routine, (void *)&index[i]);
}
}
// 等待 4 个线程执行结束
for (int i = 0; i < 4; i++)
{
pthread_join(ppid[i], 0);
}
for (int i = 0; i < 2; i++)
pthread_attr_destroy(&attr[i]);
return 0;
}
输出结果如下:同一段代码,输出结果有所不同,这里经过分析是linux实时进程的门限问题,就是说在 一段时间周期内,实时进程最多只能跑 95%的时间 ,剩下的5%需要让给普通进程,也能避免RT进程有bug, 把系统挂死了。这个就是 RT进程的熔断机制。从左图中可以看出线程1跑到一半,被线程2抢占了,线程2运行完退出,继续线程1,然后线程4和线程3又把1抢断了(这里就是5%的时间让给普通线程)。
linux线程调度碰到的一些基本问题在这里做一个总结,还有更多东西后续学习或接触到再进行补充。
参考:https://zhuanlan.zhihu.com/p/387806696
https://blog.csdn.net/Adrian503/article/details/109714119