一、Linux线程调度策略
在 Linux 中,调度策略(scheduling policy)是操作系统用来决定进程或线程调度顺序的算法。在 <sched.h>
头文件中定义了几种调度策略,其中包括以下三种常见的调度策略:
#define SCHED_OTHER 0
#define SCHED_FIFO 1
#define SCHED_RR 2
-
SCHED_OTHER
:又称为 CFS(Completely Fair Scheduler),是 Linux 默认的调度策略。它基于时间片(time-slice)的概念,使用公平调度算法,以尽量保证所有进程或线程公平地分享 CPU 时间。对于大多数普通应用程序来说,默认的SCHED_OTHER
调度策略已经足够。 -
SCHED_FIFO
:先进先出调度策略,也称为实时(real-time)调度策略之一。按照任务到达顺序依次运行,直到任务主动释放 CPU 或者被更高优先级的任务抢占。 -
SCHED_RR
:轮转调度策略,也是一种实时调度策略。每个任务都按照一定时间片轮流使用 CPU,当时间片用完后,任务会被挂起并被放置到队列的末尾,然后下一个任务开始执行。SCHED_RR
调度策略允许设置不同的任务优先级,优先级高的任务会在优先级低的任务之前执行。
这些调度策略可以通过 sched_setscheduler
函数来设置进程或线程的调度策略。以下是一个示例,展示了如何使用 sched_setscheduler
函数将进程的调度策略设置为 SCHED_FIFO
:
#include <stdio.h>
#include <sched.h>
#include <unistd.h>
int main() {
struct sched_param param;
// 设置进程的调度策略为 SCHED_FIFO
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler");
return 1;
}
// 获取当前进程的调度策略和优先级
int policy = sched_getscheduler(0);
if (policy == -1) {
perror("sched_getscheduler");
return 1;
}
int priority = sched_get_priority_max(policy);
if (priority == -1) {
perror("sched_get_priority_max");
return 1;
}
printf("调度策略: %d,优先级范围: 1 - %d\n", policy, priority);
return 0;
}
在上述示例中,通过调用 sched_setscheduler
函数将当前进程的调度策略设置为 SCHED_FIFO
。然后,通过调用 sched_getscheduler
和 sched_get_priority_max
函数获取当前进程的调度策略和允许的最高优先级。
二、SCHED_RR 为什么比 SCHED_OTHER 要实时呢
SCHED_RR
被称为轮转调度(Round-Robin Scheduling)策略,它在 Linux 中被认为是一种实时调度策略。相比于默认调度策略 SCHED_OTHER
,SCHED_RR
具有实时性的原因有以下几点:
-
时间片轮转:
SCHED_RR
使用时间片轮转的方式进行调度。每个任务被分配一个时间片,在时间片内运行,当时间片用完时,操作系统会自动切换到下一个任务。这种轮转的调度方式使得每个任务都能按照一定时间间隔获得 CPU 的使用权,提高了实时性。 -
优先级调度:
SCHED_RR
允许为不同任务设置不同的优先级。优先级高的任务在时间片内先于优先级低的任务执行。这样可以确保优先级高的实时任务能够及时得到 CPU 的调度,并避免优先级低的任务长时间占用 CPU。 -
抢占能力:
SCHED_RR
具有抢占能力,即优先级更高的任务可以抢占优先级低的任务,即使后者还没有用完时间片。这样,当出现具有更高优先级的实时任务时,操作系统可以及时地将 CPU 资源分配给它,从而提高实时性。
总体而言,相比于默认的 SCHED_OTHER
调度策略,SCHED_RR
具有更好的实时性能。它为实时应用程序提供了更精确的任务调度控制和保证,使得这些任务能够满足对响应性和实时性要求更高的应用场景。
需要注意的是,SCHED_RR
并不是 Linux 中唯一的实时调度策略,还有其他实时调度策略如 SCHED_FIFO
可供选择,具体的选择要根据应用需求和系统的实时性要求进行合理的决策。
三、如何使用线程调度相关API
1、相关API介绍
pthread_attr_init
其是一个 Linux 线程 API 函数,用于初始化线程属性对象。在使用线程属性对象之前,必须先调用该函数进行初始化。
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
`attr` 参数是指向线程属性对象的指针,该指针指向的内存空间将被用于存储线程属性信息。
在调用 `pthread_attr_init()` 函数后,`attr` 指向的线程属性对象将被初始化为默认状态,
即使用默认属性值。如果需要更改线程的属性,则可以使用其他线程属性相关的函数(如
`pthread_attr_setschedpolicy()`、`pthread_attr_setstacksize()` 等)来修改属性值。
函数返回值为 0 表示初始化成功,否则返回相应的错误代码。
pthread_attr_setschedpolicy
是一个 Linux 线程 API 函数,用于设置线程属性对象(pthread_attr_t
)中的调度策略(scheduling policy)。调度策略是指在多个线程竞争 CPU 资源时,操作系统按照一定的规则来分配 CPU 时间片的方式。
#include <pthread.h>
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
`attr` 参数是指向线程属性对象的指针,而 `policy` 参数则为想要设置的调度策略
。常见的调度策略有以下几种:
SCHED_FIFO
SCHED_RR
SCHED_OTHER
在调用 `pthread_attr_setschedpolicy()` 函数之后,线程所使用的调度策略就被设置为 policy
指定的值。需要注意的是,调度策略只能被设置一次,一旦设置就不能更改。如果需要修改调度策略,
则必须先销毁线程属性对象并重新创建一个新的属性对象。
函数返回值为 0 表示设置成功,否则返回相应的错误代码。
pthread_attr_setstacksize
其是一个 POSIX 线程库的函数,用于设置线程的堆栈大小。
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
该函数接受两个参数:
- `attr` 是一个指向线程属性对象的指针。使用 `pthread_attr_init` 初始化线程属性对象,
并在调用 `pthread_create` 创建线程时传递给它。
- `stacksize` 是一个 `size_t` 类型的值,表示期望的线程堆栈大小,以字节为单位。
该函数返回 0 表示成功,非零值表示失败。
请注意,线程堆栈大小的最小值可能会受到系统和库的限制。在使用时,应该小心选择堆栈大小以
确保适应线程所需的工作负载。
pthread_attr_setaffinity_np
其是一个 POSIX 线程库函数,在 Linux 平台上设置线程的 CPU 亲和性(CPU affinity)。CPU 亲和性指定了线程允许在哪些 CPU 核心上执行。
int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize,
const cpu_set_t *cpuset);
该函数接受三个参数:
- `attr` 是一个指向线程属性对象(`pthread_attr_t`)的指针。使用 `pthread_attr_init`
进行初始化,并在调用 `pthread_create` 时传递给它。
- `cpusetsize` 是一个 `size_t` 类型的值,表示 `cpuset` 参数的大小,以字节为单位。一般
可以使用 `sizeof(cpu_set_t)` 获取正确的大小。
- `cpuset` 是一个 `cpu_set_t` 类型的指针,表示线程允许执行的 CPU 核心集合。可以使用
`CPU_ZERO`、`CPU_SET`、`CPU_CLR` 等宏进行设置。
该函数返回 0 表示成功,非零值表示失败。
2、示例代码
提供一个使用 Linux API 实现线程调度策略、设置线程属性、绑定核心的 C 语言代码示例。下面是一个简单的示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 线程函数1
void* thread_func1(void* arg) {
printf("线程1正在运行\n");
// 线程1的具体任务逻辑
return NULL;
}
// 线程函数2
void* thread_func2(void* arg) {
printf("线程2正在运行\n");
// 线程2的具体任务逻辑
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_attr_t attr1, attr2;
// 初始化线程属性
pthread_attr_init(&attr1);
pthread_attr_init(&attr2);
// 设置线程调度策略:SCHED_FIFO为实时线程,SCHED_OTHER为普通线程
pthread_attr_setschedpolicy(&attr1, SCHED_FIFO);
pthread_attr_setschedpolicy(&attr2, SCHED_OTHER);
// 设置线程栈大小
size_t stack_size = 1024 * 1024; // 1MB
pthread_attr_setstacksize(&attr1, stack_size);
pthread_attr_setstacksize(&attr2, stack_size);
// 设置线程名称
pthread_attr_setname(&attr1, "Real-Time Thread");
pthread_attr_setname(&attr2, "Normal Thread");
// 创建线程1,绑定到核心0
cpu_set_t cpuset1;
CPU_ZERO(&cpuset1);
CPU_SET(0, &cpuset1);
pthread_attr_setaffinity_np(&attr1, sizeof(cpu_set_t), &cpuset1);
pthread_create(&thread1, &attr1, thread_func1, NULL);
// 创建线程2,绑定到核心1
cpu_set_t cpuset2;
CPU_ZERO(&cpuset2);
CPU_SET(1, &cpuset2);
pthread_attr_setaffinity_np(&attr2, sizeof(cpu_set_t), &cpuset2);
pthread_create(&thread2, &attr2, thread_func2, NULL);
// 等待线程1和线程2运行结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
// 销毁线程属性
pthread_attr_destroy(&attr1);
pthread_attr_destroy(&attr2);
return 0;
}
上述代码中,thread_func1()
和 thread_func2()
是分别作为线程1和线程2的函数。通过 pthread_t
数据类型创建线程,并使用 pthread_attr_t
数据类型来设置线程属性。
在主函数中,我们初始化了线程属性 attr1
和 attr2
,并分别为它们设置了调度策略、栈大小、线程名称和绑定的核心。通过调用 pthread_create()
函数创建线程并将线程函数和属性传递给它。
最后,通过调用 pthread_join()
函数来等待线程1和线程2运行结束,并在程序的末尾销毁线程属性。
欢迎大家指导和交流!如果我有任何错误或遗漏,请立即指正,我愿意学习改进。期待与大家一起进步!
文章评论