Linux修改调度策略和优先级

9 篇文章 0 订阅
7 篇文章 0 订阅

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, &param);
        // 输出线程的优先级
        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, &param1);
        // 输出进程的优先级
        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, &param);

        // 输出线程的优先级
        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, &param1);
        // 输出进程的优先级
        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, &param);

        // 输出线程的优先级
        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, &param1);
        // 输出进程的优先级
        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, &param);
        // 输出线程的优先级
        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, &param1);
        // 输出进程的优先级
        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, &param);

        // 输出进程的优先级
        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, &param);

    // 输出进程的优先级
    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, &param);

    // 输出进程的优先级
    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_OTHERSCHED_FIFOSCHED_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, &param);

// 输出线程的优先级
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的一点说明

  1. 使用方式:std::thread是C++11中引入的线程管理类,在C++中使用它可以方便地创建和管理线程。而pthread是C库,通过调用相关的函数来创建、终止、同步和管理线程。

  2. 功能和特性:std::thread提供了许多方便的线程管理功能,如线程创建、传递参数、获取返回值、等待线程完成等。它提供了面向对象的接口,让线程管理更加易于使用和理解。pthread是C库,提供了更底层的线程管理功能,如线程创建、同步与互斥等。

  3. 平台兼容性:std::thread是C++标准库的一部分,因此在支持C++11的编译器上可以使用,可移植性较好。而pthread是POSIX线程库,可以在UNIX和类UNIX系统上使用(例如Linux、MacOS),但在Windows上需要使用第三方库来支持。

  4. 编程风格:std::thread以面向对象的方式提供了线程管理的接口,更符合C++的编程风格,并且具有类型安全性和异常处理机制。而pthread是C库,并且使用C的函数指针和回调函数来处理线程逻辑。

std::thread在实现上可能使用底层的pthread库作为其实现方式。因此,std::thread可能在底层使用pthread来完成线程管理,但在使用上还是有一定的差异的。另外,对于简单的线程操作,std::thread通常更易于使用和理解,而对于特定的高级线程功能,可能需要使用pthread库来实现复杂的线程逻辑。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中,进程的调度策略和实时优先级可以通过调整相关参数进行设定。 进程的调度策略调度类别和调度策略两部分组成。调度类别包括实时进程和普通进程,而调度策略包括FIFO(先进先出)、RR(轮转)、以及其他一些非实时调度策略。 对于实时进程,可以通过设置调度策略和实时优先级,来决定进程的调度次序。调度策略包括SCHED_FIFO(先进先出)和SCHED_RR(轮转)两种。SCHED_FIFO策略下,实时进程的执行顺序按照进入等待队列的先后顺序,优先级越高的进程越先执行;而SCHED_RR策略下,实时进程以时间片为单位进行轮转调度,每个进程执行一个时间片后切换到下一个进程。实时进程的实时优先级范围为1-99,优先级越高的进程被调度的机会越多。 对于普通进程,可以选择使用其他非实时调度策略,如SCHED_NORMAL(普通进程默认调度策略)等。除了实时优先级外,普通进程还有一个静态优先级,范围从0-39,静态优先级越高的进程在竞争CPU资源时被CPU调度程序考虑的机会越多。 在Linux系统中,可以使用相关命令和API函数来设定进程的调度策略和实时优先级。例如,通过使用命令"chrt"可以更改进程的调度策略和实时优先级,而在编程时可以使用调度相关的函数如sched_setscheduler()和sched_setparam()来进行设定。 通过合理设置进程的调度策略和实时优先级,可以优化系统的性能,使得实时任务能够按照预定的要求运行。但需要注意的是,过高的实时优先级可能会导致系统其他进程被饿死,因此需要在设定时进行权衡和限制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值