多线程编程
我们在进行多线程编程的时间,通常先会对问题领域进行任务的拆解,深入一点的多线程编程,会涉及到任务优先级的考虑;如果再深一点,一般可能就是多核编程:Cache热度、绑核、隔离CPU等。
但多核编程与具体硬件绑定较为紧密,通用性比较差,不到万不得已的时候,多核编程实为下策,应避免用之!
在此,要介绍的是在多线程编程中调度策略编程
。相较于多核编程,可能更贴合普通的使用场景!
由来
对于某一些任务类型,例如,负载均衡器LB,这类任务有相当的密集CPU使用需求,但又不是100%的CPU消耗。在普通Linux CFS公平调度策略下,任务在时间片到时需要被换下马重新调度,在需要执行时,又因为历史上CPU使用时间的“被动”,经过动态计算优先级,又不一定能够马上被调度起来,可能会造成某些程度的丢包!
但在Linux系统分为实时系统和非实时系统,以前因为认知上的偏差,对于Linux CFS公平调度策略小小有点不满,为什么不可以实现线程调度策略定制的自由呢?
确实,Linux是自由的!近来,通过查看man 7 sched
,让自己满足地惊艳了一把,原来Linux早就实现了这种定制需求!
多核编程不见得效果更好,建议使用通用性更好的实时调度策略编程 😃
SCHED_RR实时调度策略
在Linux中调度策略分为实时调度策略和非实时策略。
深入分析了下SCHED_RR
策略,此种调度策略已经可以比较好地满足实时性要求高和CPU使用密集的负载平衡器的多线程任务场景。
直白一点说就是:对于CPU较为密集使用的场景,想在自己退让CPU使用时,才脱离CPU执行;而当想使用CPU时,又可以获得较为实时的调度,上马执行
SCHED_RR
实时调度策略,在调度等级上高于普通调度策略;同时实时任务优先级为静态优先级,不进行动态调整
通过控制内核最长使用CPU时间片时间
/proc/sys/kernel/sched_rr_timeslice_ms
,可以以控制任务在需要退让CPU使用时,才脱离CPU使用通过查看
/proc/{pid}/status
中voluntary_ctxt_switches、nonvoluntary_ctxt_switches
两个统计项,可以观察任务主动让出CPU和被动让出CPU的比值,以量化策略控制的效果
结束语
在适合的多线程场景,建议多线程编程定制合适的调度策略
附录例子
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#define __USE_GNU
#include <sched.h>
#include <signal.h>static int g_exit;
static void term_handler(int s)
{g_exit = 1;
}/** cat /proc/sys/kernel/sched_rt_runtime_us* cat /proc/sys/kernel/sched_rr_timeslice_ms* cat /sys/fs/cgroup/cpu/user.slice/cpu.rt_runtime_us* echo $((10*1000)) > /sys/fs/cgroup/cpu/user.slice/cpu.rt_runtime_us
*/
int main(int argc , char* argv[])
{char szErrorDesc[256];struct sigaction act, oldact;act.sa_handler = term_handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGINT, &act, &oldact);sigaction(SIGTERM, &act, &oldact);//signal(SIGINT, SIG_IGN);pid_t pid = getpid();printf("pid: %d, policy: %d\n", pid, sched_getscheduler(pid));int policy = -1;int opt;while((opt = getopt(argc, argv, "rb")) != -1) {switch(opt) {case 'r':policy = SCHED_RR;break;case 'b':policy = SCHED_BATCH;break;default: /* '?' */fprintf(stderr, "Usage: %s [-r --SCHED_RR] [-b --SCHED_BATCH] \n", argv[0]);return 1;}}if(policy != -1) {struct sched_param tParam = {};if(policy == SCHED_RR)tParam.sched_priority = 1;int rc = sched_setscheduler(pid, policy, &tParam);if(rc < 0){strerror_r(errno, szErrorDesc, sizeof(szErrorDesc));fprintf(stderr, "sched_setscheduler fail, errno: %d, %s", errno, szErrorDesc);}else{printf("after set thread policy: %d\n", sched_getscheduler(pid));} }char szCmd[64];snprintf(szCmd, sizeof(szCmd), "cat /proc/%d/status", pid);printf("can see %s to see the context switch\n", szCmd);uint64_t count = 0;while(g_exit == 0) {++count;}printf("cat %s to see the context switch result\n", szCmd);system(szCmd);return 0;
}