1. Linux调度策略概述
内核默认调度算法是循环时间分享策略(SCHED_OTHER或SCHED_NORMAL),实时调度策略分为两种SCHED_RR和SCHED_FIFO,linux系统中,这两个调度策略都有99个优先级,其优先级数值从1(低优先级)~ 99(高优先级),每个优先级又有一个进程队列。
SCHED_RR
同优先级的实时进程中,每个进程又是通过获得时间片来分时运行。
不同优先级的实时进程,高优先级的实时进程能够抢占低优先级的实时进程。
SCHED_FIFO
同优先级的实时进程,后进入的进程要等前一个进程释放了CPU,才能够运行。(没有时间片)
不同优先级的实时进程,高优先级的实时进程能够抢占低优先级的实时进程。
SCHED_OTHER
为进线程默认的调度策略,静态优先级为0。
写在前面:
1. 在Linux系统中,线程和进程之于Linux系统内核调度器来说是一样的,线程会当做进程一样来参与调度,也可以说线程是内核调度的最小单元。
2.子进程或子线程会继承父进程/线程的调度策略。
2. 示例
2.1 如何修改和获取进程/线程的调度策略和优先级
下面的代码为:在进程的main函数中首先通过sched_setscheduler()函数修改了进程的优先级,然后创建了4个线程,前三个线程是通过C++ std::thread进行创建,第4个线程是通过POSIX 的pthread_create API进行创建。这四个线程的主函数分别为thread_func1/2/3/4()。其中:
- 线程1不做任何修改,其继承了父线程(main)的调度策略;
- 线程2在main函数中创建完后,通过POSIX的API pthread_setschedparam调整了调度参数;
- 线程3在线程函数thread_fun3()内部,通过Linux 的API sched_setscheduler调整了调度参数;
- 线程4均使用POSIX API进行操作,在pthread_create之前通过attr设置了调度参数属性。
线程函数中通过pthread_getschedparam()和sched_getparam()两个接口分别打印了优先级信息。
#include <thread>
#include <iostream>
#include <sched.h>
#include <cstdlib>
#include <unistd.h>
std::thread clet1, clet2, clet3;
pthread_t clet4;
void thread_func1(int ret){
while(1){
// 获取当前线程的ID
pthread_t threadId = clet1.native_handle();
// 创建一个线程属性对象
pthread_attr_t attr;
pthread_attr_init(&attr);
// 获取线程的调度参数
struct sched_param param;
int policy;
pthread_getschedparam(threadId, &policy, ¶m);
// 输出线程的优先级
std::cout << "Thread 1: getschedparam priority=" << param.sched_priority << std::endl;
// 销毁线程属性对象
pthread_attr_destroy(&attr);
// 获取当前进程的调度策略
int policy1 = sched_getscheduler(0);
// 获取当前进程的调度参数
struct sched_param param1;
sched_getparam(0, ¶m1);
// 输出进程的优先级
std::cout << "Thread 1: getscheduler priority=" << param1.sched_priority << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
}
void thread_func2(int ret){
while(1){
// 获取当前线程的ID
pthread_t threadId = clet2.native_handle();
// 创建一个线程属性对象
pthread_attr_t attr;
pthread_attr_init(&attr);
// 获取线程的调度参数
struct sched_param param;
int policy;
pthread_getschedparam(threadId, &policy, ¶m);
// 输出线程的优先级
std::cout << "Thread 2: getschedparam priority=" << param.sched_priority << std::endl;
// 销毁线程属性对象
pthread_attr_destroy(&attr);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 获取当前进程的调度策略
int policy1 = sched_getscheduler(0);
// 获取当前进程的调度参数
struct sched_param param1;
sched_getparam(0, ¶m1);
// 输出进程的优先级
std::cout << "Thread 2: getscheduler priority=" << param1.sched_priority << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
}
void thread_func3(int ret){
//在线程函数内部通过sched_setscheduler()来调整调度策略
struct sched_param sp{};
sp.sched_priority = 21;
if (sched_setscheduler(0, SCHED_RR, &sp) < 0) {
perror("Failed setting scheduling policy for SCHED_RR");
return;
}
while(1){
// 获取当前线程的ID
pthread_t threadId = clet3.native_handle();
// 创建一个线程属性对象
pthread_attr_t attr;
pthread_attr_init(&attr);
// 获取线程的调度参数
struct sched_param param;
int policy;
pthread_getschedparam(threadId, &policy, ¶m);
// 输出线程的优先级
std::cout << "Thread 3: getschedparam priority=" << param.sched_priority << std::endl;
// 销毁线程属性对象
pthread_attr_destroy(&attr);
// 获取当前进程的调度策略
int policy1 = sched_getscheduler(0);
// 获取当前进程的调度参数
struct sched_param param1;
sched_getparam(0, ¶m1);
// 输出进程的优先级
std::cout << "Thread 3: getscheduler priority=" << param1.sched_priority << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
}
void *thread_func4(void *arg){
while(1){
// 创建一个线程属性对象
pthread_attr_t attr;
pthread_attr_init(&attr);
// 获取线程的调度参数
struct sched_param param;
int policy;
pthread_getschedparam(clet4, &policy, ¶m);
// 输出线程的优先级
std::cout << "Thread 4: getschedparam priority=" << param.sched_priority << std::endl;
// 销毁线程属性对象
pthread_attr_destroy(&attr);
// 获取当前进程的调度策略
int policy1 = sched_getscheduler(0);
// 获取当前进程的调度参数
struct sched_param param1;
sched_getparam(0, ¶m1);
// 输出进程的优先级
std::cout << "Thread 4: getscheduler priority=" << param1.sched_priority << std::endl;
sleep(5);
}
}
int main()
{
int ret = -1;
//1.在创建子线程之前,设置调度参数,这样所有的子线程默认都会继承此调度参数
struct sched_param sp{};
sp.sched_priority = 39;
if (sched_setscheduler(0, SCHED_RR, &sp) < 0) {
perror("Failed setting scheduling policy for SCHED_RR");
return ret;
}
std::thread clet1(thread_func1, ret);
std::thread clet2(thread_func2, ret);
std::thread clet3(thread_func3, ret);
//2.修改线程2的调度策略和优先级
sched_param sch_params;
sch_params.sched_priority = 33;
pthread_setschedparam(clet2.native_handle(), SCHED_RR, &sch_params);
//3.通过POSIX pthread系列API的c方式设置调度参数和创建线程
pthread_attr_t attr{};
pthread_attr_init(&attr);
struct sched_param sp4 {};
sp4.sched_priority = 37;
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&attr, SCHED_RR);
if (pthread_attr_setschedparam(&attr, &sp4) < 0) {
perror("Failed setting scheduling policy for SCHED_RR");
return ret;
}
pthread_create(&clet4, &attr, thread_func4, nullptr);
while(1){
// 获取当前进程的调度策略
int policy = sched_getscheduler(0);
// 获取当前进程的调度参数
struct sched_param param;
sched_getparam(0, ¶m);
// 输出进程的优先级
std::cout << "main priority: " << param.sched_priority << std::endl;
sleep(5);
continue;
}
return 0;
}
优先级输出结果展示:
从测试结果来看,通过std::thread创建完线程后,通过POSIX的API pthread_setschedparam调整了调度参数(thread 2) 和 在线程函数内部通过Linux的API sched_setscheduler都能调整线程的调度策略。
2.2 不同优先级和调度策略的效果测试
本例中,在main函数中启用了两个线程clet1和clet2,分别执行线程函数thread_func1和thread_func2,线程中都是5微秒,循环计数。
clet1:默认的调度策略
clet2:SCHED_RR,优先级为40
在main函数中注册“ctrl+c”的信号捕获函数,并通过改变“g_flag”来停止两个线程,并打印线程的调度策略、优先级和计数值,以此来对比不同调度策略和优先级对于线程执行时间的影响。
#include <thread>
#include <iostream>
#include <unistd.h>
#include <csignal>
std::thread clet1, clet2;
bool g_flag{true};
static std::uint64_t cunt1{}, cunt2{};
void thread_func1(int ret){
while(g_flag){
cunt1++;
std::this_thread::sleep_for(std::chrono::microseconds(5));
}
// 获取当前进程的调度策略
int policy = sched_getscheduler(0);
// 获取当前进程的调度参数
struct sched_param param;
sched_getparam(0, ¶m);
// 输出进程的优先级
std::cout << "Thread 1: priority= " << param.sched_priority << std::endl;
switch(policy){
case SCHED_OTHER:
std::cout<<"Thread 1: policy=SCHED_OTHER\n"; break;
case SCHED_RR:
std::cout<<"Thread 1: policy=SCHED_RR\n"; break;
case SCHED_FIFO:
std::cout<<"Thread 1: policy=SCHED_FIFO\n"; break;
default:
std::cout<<"Thread 1: policy=unknown\n"; break;
}
std::cout<<"\n"<<"Thread 1: count="<<cunt1<<"\n";
}
void thread_func2(int ret){
while(g_flag){
cunt2++;
std::this_thread::sleep_for(std::chrono::microseconds(5));
}
// 获取当前进程的调度策略
int policy = sched_getscheduler(0);
// 获取当前进程的调度参数
struct sched_param param;
sched_getparam(0, ¶m);
// 输出进程的优先级
std::cout << "Thread 2: priority= " << param.sched_priority << std::endl;
switch(policy){
case SCHED_OTHER:
std::cout<<"Thread 2: policy=SCHED_OTHER\n"; break;
case SCHED_RR:
std::cout<<"Thread 2: policy=SCHED_RR\n"; break;
case SCHED_FIFO:
std::cout<<"Thread 2: policy=SCHED_FIFO\n"; break;
default:
std::cout<<"Thread 2: policy=unknown\n"; break;
}
std::cout<<"\n"<<"Thread 2: count="<<cunt2<<"\n";
}
static void term_stop_handler(int signo) {
std::cout<<"recv signo is "<<signo<<std::endl;
g_flag = false;
if (clet1.joinable()) clet1.join();
if (clet2.joinable()) clet2.join();
signal(signo, SIG_DFL);
}
int main()
{
int ret = -1;
signal(SIGINT, term_stop_handler); // ctrl+c sig
pthread_t td_1, td_2;
std::thread clet1(thread_func1, ret);
std::thread clet2(thread_func2, ret);
sched_param sch_params;
sch_params.sched_priority = 30;
pthread_setschedparam(clet2.native_handle(), SCHED_RR, &sch_params);
while(1){
sleep(1);
continue;
}
return 0;
}
编译上述代码并运行,执行一段时间后,通过 “ctrl+c” 停掉进程,输出结果如下:
通过计数可以看出,线程clet2的执行时间大概是线程clet1的10倍之久。
3. 关于修改/获取调度参数的API的说明
3.1.修改调度参数
pthread_setschedparam() 和 sched_setscheduler()的关系
pthread_setschedparam() 和 sched_setscheduler() 都可以修改线程的调度策略,区别如下:
函数定义:
- pthread_setschedparam()函数是POSIX线程库提供的函数,用于设置线程的调度参数,它使用pthread_t 类型的线程ID来标识要设置的线程。函数的原型为:
int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param);
sched_setscheduler()
函数是Linux操作系统提供的函数,允许设置进程的调度策略和参数。它使用进程的ID来标识要设置的进程。函数原型为:
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
调用地点:
- pthread_setschedparam()只能在目标线程的主线程外去设置,创建完线程后然后调用。
- sched_setscheduler()在线程函数中去设置,第一个参数设为0就表示当前线程。
3.2 获取调度参数
3.2.1 sched_getscheduler --获取调度策略
sched_getscheduler
是一个Linux系统调用,用于获取指定进程的调度策略。
其函数原型为:
int sched_getscheduler(pid_t pid);
参数 pid
是要查询调度策略的进程的进程ID,如果给定的进程ID为0,表示获取当前进程/线程自身的调度策略。
调用成功时,返回当前进程的调度策略。可能的返回值如下:
SCHED_OTHER
:普通调度策略(CFS调度)SCHED_FIFO
:FIFO调度策略SCHED_RR
:轮转调度策略SCHED_BATCH
:批处理调度策略SCHED_IDLE
:空闲调度策略
调用失败时,返回-1,并设置errno来指示错误的原因。
说明:sched_getscheduler
只返回调度策略,不提供有关进程的其他调度信息。
3.2.2 sched_getparam --获取调度参数
sched_getparam
是一个Linux系统调用,用于获取指定进程的调度参数。
其函数原型为:
int sched_getparam(pid_t pid, struct sched_param *param);
参数 pid
是要查询调度参数的进程的进程ID,如果给定的进程ID为0,表示获取当前进程/线程自身的调度参数。
参数 param
是一个 sched_param
结构的指针,用于接收查询到的调度参数。
调用成功时,返回0,并将查询到的调度参数存储在 param
结构中。sched_param
结构定义如下:
struct sched_param {
int sched_priority; // 优先级
};
调用失败时,返回-1,并设置errno来指示错误的原因。
说明:sched_getparam
只返回调度参数,不提供有关进程的其他调度信息。调度参数是一个整数,表示进程的优先级。优先级较高的进程在调度时会被优先考虑。
3.2.3 pthread_getschedparam --获取线程调度参数
pthread_getschedparam
是一个POSIX线程库函数,用于获取指定线程的调度参数。
其函数原型为:
int pthread_getschedparam(pthread_t thread, int *policy, struct sched_param *param);
参数 thread
是要查询调度参数的目标线程的pthread_t类型标识符。
参数 policy
是一个整数指针,用于接收查询到的调度策略。
参数 param
是一个 sched_param
结构的指针,用于接收查询到的调度参数。
调用成功时,返回0,并将查询到的调度策略存储在 policy
中,将查询到的调度参数存储在 param
结构中。
调度策略定义的值通常与 sched_getscheduler
中的值相同,例如 SCHED_OTHER
, SCHED_FIFO
, SCHED_RR
等。
调用失败时,返回一个非零的错误码,表示发生了错误。
说明:
pthread_getschedparam
只能获取查询到的线程的调度参数,不提供关于其他线程的调度信息。- 想要使用pthread_getschedparam来获取调度参数,则必须是使用POSIX的一系列API来创建线程和设置调度参数。(无论以何种方式创建线程(pthread or std::thread)、设置调度参数,使用sched_getscheduler都可以获取到正确的调度参数)
用法:
// 获取当前线程的ID,这里的my_thread为std::thread对象,其支持通过native_handle()来获取pthread_t类型的ID
pthread_t threadId = my_thread.native_handle();
// 创建一个线程属性对象
pthread_attr_t attr;
pthread_attr_init(&attr);
// 获取线程的调度参数
struct sched_param param;
int policy;
pthread_getschedparam(threadId, &policy, ¶m);
// 输出线程的优先级
std::cout << "Thread priority=" << param.sched_priority << std::endl;
3.2.4 pthread_attr_setinheritsche --设置线程关于调度参数的继承属性
pthread_attr_setinheritsched
是一个POSIX线程库函数,用于设置线程属性中的继承调度属性。
其函数原型为:
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inherit);
参数 attr
是一个指向 pthread_attr_t
类型的线程属性对象的指针,用于设置继承调度属性。
参数 inherit
是一个整数,指定了线程的调度属性是否继承自创建它的线程。
- 如果
inherit
的值为PTHREAD_INHERIT_SCHED
,则新线程将继承调用线程的调度属性。 - 如果
inherit
的值为PTHREAD_EXPLICIT_SCHED
,则新线程将使用在线程属性对象中显式设置的调度属性。
调用成功时,返回0,表示设置继承调度属性成功。
调用失败时,返回非零错误码,表示发生了错误。
说明:设置继承调度属性对已经创建的线程没有影响,仅对后续使用该线程属性对象创建的线程起作用。
3.3 关于pthread和std::thread的一点说明
-
使用方式:
std::thread
是C++11中引入的线程管理类,在C++中使用它可以方便地创建和管理线程。而pthread
是C库,通过调用相关的函数来创建、终止、同步和管理线程。 -
功能和特性:
std::thread
提供了许多方便的线程管理功能,如线程创建、传递参数、获取返回值、等待线程完成等。它提供了面向对象的接口,让线程管理更加易于使用和理解。pthread
是C库,提供了更底层的线程管理功能,如线程创建、同步与互斥等。 -
平台兼容性:
std::thread
是C++标准库的一部分,因此在支持C++11的编译器上可以使用,可移植性较好。而pthread
是POSIX线程库,可以在UNIX和类UNIX系统上使用(例如Linux、MacOS),但在Windows上需要使用第三方库来支持。 -
编程风格:
std::thread
以面向对象的方式提供了线程管理的接口,更符合C++的编程风格,并且具有类型安全性和异常处理机制。而pthread
是C库,并且使用C的函数指针和回调函数来处理线程逻辑。
std::thread
在实现上可能使用底层的pthread
库作为其实现方式。因此,std::thread
可能在底层使用pthread
来完成线程管理,但在使用上还是有一定的差异的。另外,对于简单的线程操作,std::thread
通常更易于使用和理解,而对于特定的高级线程功能,可能需要使用pthread
库来实现复杂的线程逻辑。