百度自动驾驶apollo源码解读3:进程或线程设置调度策略、优先级

1.利用setpriority调整“进程”优先级,测试优先级对进程的影响(注:是进程而不是线程)

定义函数 int setpriority(int which,int who, int prio);

参数1 : PRIO_PROCESS who为进程识别码 PRIO_PGRP who 为进程的组识别码 PRIO_USER who为用户识别码

参数2 :

参数3 :prio介于-20至20之间。代表进程执行优先权,数值越低代表有较高的优先次序,执行会较频繁。此优先权默认是0,而只有超级用户(root)允许降低此值。

返回值:执行成功则返回0,如果有错误发生返回值则为-1,错误原因存于errno。

疑问:通过下面的测试代码可以看到设置进程优先级应该是成功了的,理论上优先级高的是不是应该获得更多的CPU执行时间?然而事实测试并不是!目前还不知道为什么,先做下记录日后知道了更新上

//编译指令:g++ main.cpp -lpthread -std=c++17
//运行指令:sudo ./a.out

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <pthread.h>
#include <sys/resource.h>
#include <iostream>
#include <thread>
#include <sys/syscall.h>

static void cpuidle_loop(int arg)
{
    // int ret = setpriority(PRIO_PROCESS, std::this_thread::get_id(), arg);
    // int ret = setpriority(PRIO_PROCESS, syscall(SYS_gettid), arg);
    int ret = setpriority(PRIO_PROCESS, 0, arg);
    if (ret != 0)
        std::cout << "setpriority failed !  arg -> " << arg << "  errno -> " << errno << std::endl;
    int prio = getpriority(PRIO_PROCESS, 0);
    unsigned long long int loopCnt = 0;
    int print_count = 0;
    do
    {
        loopCnt++;
        if (0 == (loopCnt % 100000000))
        {
            printf("pri[%d] loopCnt=%lld prio=%d print_count=%d\n", arg, loopCnt, prio, ++print_count);
        }
    } while (1);
}

int main(int argn, char *argc[])
{
    int ret = fork();
    if (ret == 0)
    {
        std::thread t1(cpuidle_loop, 19);
        t1.join();
    }
    else if (ret > 0)
    {
        std::thread t2(cpuidle_loop, -19);
        t2.join();
    }

    return 0;
}

2.利用sched_setscheduler设置进程调度策略

定义函数:int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);

参数1:进程id,如果pid等于零,则将设置调用线程的调度策略和参数

参数2:非实时调度策略:
    SCHED_OTHER:标准循环分时策策;( 默认的Linux分时调度)
        只能以静态优先级0使用。SCHED_OTHER是标准的Linux分时调度程序,适用于不需要特殊实时机制的所有线程。 基于仅在此列表内确定的动态优先级,从静态优先级0列表中选择要运行的线程。动态优先级基于nice值(由nice(2) 或 setpriority(2)设置),并在线程准备好运行但每次被调度程序拒绝运行时都增加。 这样可以确保所有 SCHED_OTHER 线程之间取得公平的处理。
    SCHED_BATCH:用于“批处理”样式的进程执行;
        从Linux 2.6.16开始。)SCHED_BATCH 仅可用于静态优先级0。此策略与 SCHED_OTHER 类似,因为它根据线程的动态优先级(基于nice值)调度线程。 区别在于,此策略将使调度程序始终假定线程占用大量CPU。因此,调度程序将对唤醒行为施加较小的调度损失,从而使该线程在调度决策中受到轻微影响。此策略对于非交互式但又不想降低其合理价值的工作负载,以及需要确定性调度策略而又不会引起交互(导致工作负载之间的任务)的交互性的工作负载非常有用。
    SCHED_IDLE:用于运行优先级较低的后台作业。
        (自Linux 2.6.23开始)SCHED_IDLE 仅可以在静态优先级0上使用;进程的nice值对此策略没有影响。该策略旨在以极低的优先级运行作业(对于 SCHED_OTHER 或 SCHED_BATCH 策略,该值甚至低于+19 nice值)。
    实时调度策略:
    SCHED_FIFO:先进先出策略;只能在静态优先级高于0的情况下使用,这意味着当SCHED_FIFO线程变为可运行时,它将始终立即抢占任何当前正在运行的 SCHED_OTHER,SCHED_BATCH 或 SCHED_IDLE 线程。 SCHED_FIFO 是一种简单的调度算法,无需进行时间分片。 对于根据 SCHED_FIFO策略调度的线程,适用以下规则:
        (1)被另一个更高优先级的线程抢占的 SCHED_FIFO 线程将保持其优先级在列表的开头,并在所有更高优先级的线程阻塞时立即恢复执行。
        (2)当SCHED_FIFO线程变为可运行线程时,将根据优先级将其插入列表的末尾。
        (3)调用sched_setscheduler() 或 sched_setparam() 会将pid标识的SCHED_FIFO(或SCHED_RR)线程放在列表的开头(如果可运行)。 因此,如果具有相同优先级的其它进程,它可能会抢占当前正在运行的线程。(POSIX.1-2001指定该线程应转到列表的末尾。)
        (4)调用sched_yield()的线程将放在列表的末尾。
        没有其他事件会在静态优先级相等的可运行线程的等待列表中移动以 SCHED_FIFO 策略调度的线程。SCHED_FIFO 线程将一直运行,直到被I/O请求阻止,被更高优先级的线程抢占或调用 sched_yield(2)。
    SCHED_RR:循环策略。是 SCHED_FIFO 的简单增强。上面针对 SCHED_FIFO 所述的所有内容也适用于 SCHED_RR,除了允许每个线程仅在最大时间范围内运行。 如果 SCHED_RR 线程已经运行了等于或大于时间范围的时间段,则将其放在其优先级列表的末尾。 SCHED_RR 线程已被更高优先级的线程抢占,并随后在运行线程时恢复执行,将继续执行完成其循环时间范围的未到期部分。 可以使用 sched_rr_get_interval(2)获取时间量的长度。

参数3:

注:需要root权限才能设置成功。

//编译指令:g++ main.cpp -lpthread -std=c++17
//运行指令:sudo ./a.out

#include <iostream>
#include <thread>
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argn, char *argc[])
{
    struct sched_param param;
    int maxpri, count;

    maxpri = sched_get_priority_max(SCHED_FIFO);
    if (maxpri == -1)
    {
        perror("sched_get_priority_max() failed");
        return -1;
    }
    printf("max priority of SCHED_FIFO is %d\n", maxpri);

#if 1
    param.sched_priority = maxpri;
    if (sched_setscheduler(getpid(), SCHED_FIFO, &param) == -1)
    {
        perror("sched_setscheduler() failed");
        return -1;
    }
#endif

    fork();
    fork();

    while (1)
    {
        count++;
    }
    return 0;
}

执行结果:

#if 1的时候非常卡,top执行结果
 PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                         
  3819 root      rt   0    4208    624    556 R  96.1  0.0   0:29.90 pp                                                   
  3820 root      rt   0    4208     80      0 R  95.7  0.0   0:29.54 pp                                                   
  3821 root      rt   0    4208     80      0 R  93.7  0.0   0:29.26 pp                                                   
  3822 root      rt   0    4208     80      0 R  93.7  0.0   0:29.16 pp   
#if 0时不卡,top执行结果
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                       
  3830 root      20   0    4208     84      0 R  99.8  0.0   0:08.71 pp                                                   
  3831 root      20   0    4208     84      0 R  99.8  0.0   0:09.02 pp                                                   
  3832 root      20   0    4208     84      0 R  99.4  0.0   0:08.63 pp  

注意:上述的卡是有概率的,我的电脑是12核心的,现在其中4个核心霸占,其他进程有正好也在其中4个核心上面就会很卡

3.利用pthread_setschedparam设置线程的调度策略和优先级

定义函数:int pthread_setschedparam(pthread_t target_thread, int policy, const struct sched_param *param)

参数1:使用pthread_create所获得的线程ID

参数2:

线程的调度有三种策略:SCHED_OTHER、SCHED_RR和SCHED_FIFO。Policy用于指明使用哪种策略。下面我们简单的说明一下这三种调度策略。

SCHED_OTHER

它是默认的线程分时调度策略,所有的线程的优先级别都是0,线程的调度是通过分时来完成的。简单地说,如果系统使用这种调度策略,程序将无法设置线程的优先级。请注意,这种调度策略也是抢占式的,当高优先级的线程准备运行的时候,当前线程将被抢占并进入等待队列。这种调度策略仅仅决定线程在可运行线程队列中的具有相同优先级的线程的运行次序。

SCHED_FIFO

它是一种实时的先进先出调用策略,且只能在超级用户下运行。这种调用策略仅仅被使用于优先级大于0的线程。它意味着,使用SCHED_FIFO的可运行线程将一直抢占使用SCHED_OTHER的运行线程J。此外SCHED_FIFO是一个非分时的简单调度策略,当一个线程变成可运行状态,它将被追加到对应优先级队列的尾部((POSIX 1003.1)。当所有高优先级的线程终止或者阻塞时,它将被运行。对于相同优先级别的线程,按照简单的先进先运行的规则运行。我们考虑一种很坏的情况,如果有若干相同优先级的线程等待执行,然而最早执行的线程无终止或者阻塞动作,那么其他线程是无法执行的,除非当前线程调用如pthread_yield之类的函数,所以在使用SCHED_FIFO的时候要小心处理相同级别线程的动作。

SCHED_RR

鉴于SCHED_FIFO调度策略的一些缺点,SCHED_RR对SCHED_FIFO做出了一些增强功能。从实质上看,它还是SCHED_FIFO调用策略。它使用最大运行时间来限制当前进程的运行,当运行时间大于等于最大运行时间的时候,当前线程将被切换并放置于相同优先级队列的最后。这样做的好处是其他具有相同级别的线程能在“自私“线程下执行。

参数3:是struct sched_param类型的指针,它仅仅包含一个成员变sched_priority,指明所要设置的静态线程优先级。

//编译指令:g++ main.cpp -lpthread -std=c++17
//运行指令:sudo ./a.out

#include <thread>
#include <mutex>
#include <iostream>
#include <chrono>
#include <cstring>
#include <pthread.h>
 
std::mutex iomutex;
void f(int num)
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
 
    sched_param sch;
    int policy; 
    pthread_getschedparam(pthread_self(), &policy, &sch);
    std::lock_guard<std::mutex> lk(iomutex);
    std::cout << "Thread " << num << " is executing at priority "
              << sch.sched_priority << '\n';
}
 
int main()
{
    std::thread t1(f, 1), t2(f, 2);
 
    sched_param sch;
    int policy; 
    pthread_getschedparam(t1.native_handle(), &policy, &sch);
    sch.sched_priority = 20;
    if (pthread_setschedparam(t1.native_handle(), SCHED_FIFO, &sch)) {
        std::cout << "Failed to setschedparam: " << std::strerror(errno) << '\n';
    }
 
    t1.join(); t2.join();
    return 0;
}

总结:

上述例子中,三个都可以正常的编译和运行,但是深入的理解还是挺困难的,比如例子1设置了优先级之后具体会有什么变化?例子3也是一样的设置成功之后没法体验前后差别,例子2还稍微好点,有体验到前后差别。在此先做记录吧,先熟悉这3套API,后期有什么感悟及时补充上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值