C/C++线程绑核详解

news/2024/2/29 4:09:13

        在一些大型的工程或者特殊场景中,我们会听到绑核,绑核分为进程绑核和线程绑核。绑核的最终目的都是为了提高程序和性能或者可靠性。

一:为什么需要绑核

        操作系统发展至今,已经能很好的平衡运行在操作系统上层的应用,兼顾性能和可靠性。一般情况下,在应用程序中只需使用缺省的调度器即可。然而,在某些场景下操作系统默认的调度算法会造成程序的瓶颈,要突破这个瓶颈可能要修改这些缺省行为以实现性能上的突破。以下三个场景比较适合去绑核操作。

1,大量的计算

        基于大量计算的情形通常出现在科学和理论计算中,但是通用领域的计算也可能出现这种情况。一个常见的标志是您发现自己的应用程序要在多处理器的机器上花费大量的计算时间。

 2,测试复杂的应用程序

        测试复杂软件是我们对内核的亲和性(affinity)技术感兴趣的另外一个原因。考虑一个需要进行线性可伸缩性测试的应用程序。有些产品声明可以在 使用更多硬件 时执行得更好。

3,正在运行时间敏感的、决定性的进程 

        我们对 CPU 亲和性(affinity)感兴趣的最后一个原因是实时(对时间敏感的)进程。例如,您可能会希望使用硬亲和性(affinity)来指定一个 8 路主机上的某个处理器,而同时允许其他 7 个处理器处理所有普通的系统调度。这种做法确保长时间运行、对时间敏感的应用程序可以得到运行,同时可以允许其他应用程序独占其余的计算资源。

        在DPDK中,由于需要频繁的去收发包,为了提高收发包的性能,绑核操作在DPDK中非常的常见。DPDK通过把线程绑定到逻辑核的方法来避免跨核任务中的切换开销,减少线程调度的开销,来提高性能。 

二:绑核的基本概念

1,CPU的亲和性

        在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性。Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。

        上面提到了软亲和性,那对应的就有硬亲和性。soft affinity和hard affinity。soft affinity仅是一个建议,如果不可避免,调度器还是会把进程调度到其它的CPU上。hard affinity是调度器必须遵守的规则。

2,CPU亲和性掩码

        在绑核的操作中会经常看到mask这个单词,可能初学的不太理解这个mask代表的意义。学过TCP/IP的应该知道mask在ip中就是掩码的意思,利用掩码来划分子网,本质上就是用1和对应位上做&操作。

        在 Linux 内核中,所有的进程都有一个相关的数据结构,称为 task_struct。这个结构非常重要,原因有很多;其中与 亲和性(affinity)相关度最高的是 cpus_allowed 位掩码。这个位掩码由 n 位组成,与系统中的 n 个逻辑处理器一一对应。 具有 4 个物理 CPU 的系统可以有 4 位。如果这些 CPU 都启用了超线程,那么这个系统就有一个 8 位的位掩码。

        如果为给定的进程设置了给定的位,那么这个进程就可以在相关的 CPU 上运行。因此,如果一个进程可以在任何 CPU 上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是 1。实际上,这就是 Linux 中进程的缺省状态。

        我们可以查看自己服务器有多少个核心

[ftz@host6 ~]$ cat /proc/cpuinfo |grep processor | wc -l
64
[ftz@host6 ~]$ nproc
64

 3,绑核的代码接口

 以下是绑核的常用宏接口:

//结构体cpu_set_t,定义了CPU核心集合的数据结构
cpu_set_t *set//初始化cpu set集合
void CPU_ZERO (cpu_set_t *set); //单个cpu加到集合中
void CPU_SET (int cpu, cpu_set_t *set);//单个cpu从集中移出
void CPU_CLR (int cpu, cpu_set_t *set); //判断某个cpu是否在cpu集中
int CPU_ISSET (int cpu, const cpu_set_t *set); 

        cpu集可以认为是一个掩码,每个设置的位都对应一个可以合法调度的 cpu,而未设置的位则对应一个不可调度的 CPU。换而言之,线程都被绑定了,只能在那些对应位被设置了的处理器上运行。通常,掩码中的所有位都被置位了,也就是可以在所有的cpu中调度。

 线程绑核用到的函数接口:

long sched_setaffinity(pid_t pid, const struct cpumask *in_mask)

        该函数设置进程为pid的这个进程,让它运行在mask所设定的CPU上.如果pid的值为0,则表示指定的是当前进程,使当前进程运行在mask所设定的那些CPU上.第二个参数cpusetsize是mask所指定的数的长度.通常设定为sizeof(cpu_set_t).如果当前pid所指定的进程此时没有运行在mask所指定的任意一个CPU上,则该指定的进程会从其它CPU上迁移到mask的指定的一个CPU上运行. 

long sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask) 

         该函数获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中.即获得指定pid当前可以运行在哪些CPU上.同样,如果pid的值为0.也表示的是当前进程

三:C语言代码实现线程绑核

 代码实现:起三个线程绑到三个cpu上

#define _GNU_SOURCE  //该宏定义必须有
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <unistd.h>
#include <pthread.h>#define CREATE_THREAD_NUMBER 3
#define JUDGE_IS_SUCCESS(RET) (RET == -1)cpu_set_t g_cpuset;  //定义cpu集合struct thread_info
{pthread_t thread_id;int thread_num;
};void do_thing_busy_cpu()
{int j = 0;int buf[10240];while(j++ < 1024*1024){memset(buf, 0, sizeof(buf));}
}void *threadFunc(void *arg)
{struct thread_info *subThreadInfo = (struct thread_info *)arg;pthread_t subThreadID = subThreadInfo->thread_id;CPU_SET(subThreadInfo->thread_num, &g_cpuset); //将编号为subThreadInfo->thread_num的cpu核加到cpuset集合中if(JUDGE_IS_SUCCESS(sched_setaffinity(getpid(),sizeof(cpu_set_t),&g_cpuset))){printf("ERROR:set CPU affinity fail!\n");}while(1){cpu_set_t getCpuset;CPU_ZERO(&getCpuset);if(JUDGE_IS_SUCCESS(sched_getaffinity(getpid(),sizeof(cpu_set_t),&getCpuset))){printf("ERROR:get CPU affinity fail!\n");}for(int i=0; i<CREATE_THREAD_NUMBER; i++){if(CPU_ISSET(i,&getCpuset)){printf("thread %d is running on cpu: %d\n",subThreadInfo->thread_id,i);do_thing_busy_cpu();}}        }return NULL;
}int main(int argc, char *argv[])
{struct thread_info threadInfo[CREATE_THREAD_NUMBER];int cpuNum = sysconf(_SC_NPROCESSORS_CONF);printf("cpu number:%d\n",cpuNum);CPU_ZERO(&g_cpuset); //初始化cpu集合//创建3个线程for(int i=0; i<CREATE_THREAD_NUMBER; i++){threadInfo[i].thread_num = i;pthread_create(&threadInfo[i].thread_id,NULL,threadFunc,&threadInfo[i]);}for(int i=0; i<CREATE_THREAD_NUMBER; i++){pthread_join(threadInfo[i].thread_id,NULL);}
}

编译:

gcc bindcpu.c -o bindcpu -lpthread -std=c99

运行结果:

cpu number:64
thread 1235138304 is running on cpu: 0
thread 1226745600 is running on cpu: 0
thread 1218352896 is running on cpu: 0
thread 1235138304 is running on cpu: 0
thread 1226745600 is running on cpu: 1
thread 1218352896 is running on cpu: 1
thread 1235138304 is running on cpu: 1
thread 1226745600 is running on cpu: 0
thread 1218352896 is running on cpu: 2
thread 1235138304 is running on cpu: 2
thread 1226745600 is running on cpu: 1
thread 1218352896 is running on cpu: 0
thread 1235138304 is running on cpu: 0
thread 1226745600 is running on cpu: 2
thread 1218352896 is running on cpu: 1
thread 1235138304 is running on cpu: 1
thread 1226745600 is running on cpu: 0
thread 1218352896 is running on cpu: 2
thread 1235138304 is running on cpu: 2
thread 1226745600 is running on cpu: 1
...

可以用top命令查看进程是不是占用了三个cpu

 按1查看cpu核使用详情,由于我用的服务器是共享的大家都在上面用,所以直观的看不出来

 可以通过taskset命令来查看我们程序的CPU亲和度:

[ftz@host6 ~]$ taskset -p 1376833
pid 1376833's current affinity mask: 7

7的二进制就是111,正好符合我们的预期

 除了用代码绑核,也可以用shell命令taskset来设置进程的 CPU 亲和度 (affinity),它可以将一个进程绑定到某个特定的 CPU 核心或一组 CPU 核心。

1,绑定一个进程到单个 CPU 核心:
taskset -c <core_id> <command>
例:将进程绑定到第 1 个 CPU 核心上
taskset -c 0 command2,绑定一个进程到多个 CPU 核心
taskset -c <core_id_list> <command>
例:进程绑定到第 1、2、4 个 CPU 核心上
taskset -c 0,1,3 command3,显示一个进程当前的 CPU 亲和度:
taskset -p <pid>4,修改一个正在运行的进程的 CPU 亲和度:
taskset -cp <core_id_list> <pid>
例:将进程 12345 绑定到第 1、2、4 个 CPU 核心上
taskset -cp 0,1,3 12345

https://www.jiucaihua.cn/news/show-4626213.html

相关文章

FreeRTOS进阶学习

一、FreeRTOS内核控制控制函数&#xff1a; 1、请求任务切换函数&#xff1a; 函数原型&#xff1a;#define taskYIELD() 函数解析&#xff1a;实际上是一个宏定义的函数&#xff0c;调用一次会触发pendSV中断来实现任务切换&#xff1b; /// 2、在任务中进入临界区函数&…

javascript基础十七:bind、call、apply 区别?如何实现apply、call、bind?

一、作用 call、apply、bind作用是改变函数执行时的上下文&#xff0c;简而言之就是改变函数运行时的this指向 那么什么情况下需要改变this的指向呢&#xff1f; 举个粟子&#xff1a; var name 小爱同学 undefined const obj {name:allen ye,say:function(){console.log(th…

全局流控 or 端到端拥塞控制

同事推荐一篇论文 Bolt: Sub-RTT Congestion Control for Ultra-Low Latency&#xff0c;写点想法。 端到端原则使网络在拥塞控制中始终扮演配角&#xff0c;人们认为拥塞控制是端到端的事。几十年来人们设计的拥塞控制机制始终围绕 “主机在什么情况下要增减 cwnd” 打转。但…

2023 华为 Datacom-HCIE 真题题库 07/12--含解析

多项选择题 1.[试题编号&#xff1a;190187] &#xff08;多选题&#xff09;如图所示的拓扑采用了VXLAN分布式网关&#xff0c;SW1上的VBDIF10配置了&#xff1a;arp-proxy local enable命令&#xff0c;则以下描述中正确的有哪些项&#xff1f; A、SW1收到PC1发往PC2的报文&…

【计算机组成原理·笔记】总线控制

总线控制 总线上连接多个部件&#xff0c;为了解决&#xff1a; 什么时候由哪个部件发送信息如何给信息传送定时如何防止信息丢失如何避免多个部件同时发送如何规定接受信息的部件 等一些列问题&#xff0c;需要总线控制线进行统一管理&#xff0c;这就是总线控制&#xff0…

angular环境安装 (含nodejs详细安装步骤)

在安装本次环境之前&#xff0c;需要先把本机上的nodejs环境卸载&#xff0c;环境变量手动删除&#xff01;安装过程种环境才不会产生副作用&#xff01;实际项目安装的一次记录&#xff0c;踩了太多坑&#xff0c;记录一下&#xff0c;旨在记录&#xff01;项目需要两个不用版…

网络重置后无法上网,以太网和无线网全部丢失,网络适配器出现“56”错误码

文章目录 一、问题描述电脑系统&#xff1a;电脑问题&#xff1a;解决方案 二、问题过程1. IP问题2.网络重置问题3.电脑无法启动问题 三、解决方案1.卸载2.安全模式检查修复3.软件下载1.CCleaner2.驱动精灵万能网卡版 四、参考链接 一、问题描述 电脑系统&#xff1a; Window…

一次诡异405 METHOD_NOT_ALLOWED “Request method ‘POST‘ not supported“问题排查记录

概述 任何稍微只要有一点经验的开发者都知道HTTP 405&#xff0c;表示方法不支持。如&#xff0c;本来是定义为POST接口&#xff0c;前端使用GET请求&#xff0c;就会报错。 但是我还真遇上一次405 METHOD_NOT_ALLOWED "Request method POST not supported"问题&am…

【考研·数据结构】408真题 (2012年42题) 寻找第一个公共后缀的位置】

代码思路&#xff1a; 代码思路如下&#xff1a; 定义两个指针 p 和 q 分别指向链表 str1 和 str2 的第一个节点&#xff08;即头结点的下一个节点&#xff09;&#xff0c;并进行循环。对于每个节点 p&#xff0c;都将指针 q 重新指向链表 str2 的第一个节点&#xff0c;然后…

红黑树的 概念性质 和 详解实现(插入旋转等)

文章目录 概念满足的条件性质实现红黑树的定义红黑树节点插入操作情况一情况二情况三Insert()总代码 其余操作左右单旋RotateL 左单旋RotateR 右单旋prevCheck 红黑树性质检测isBalance 红黑树平衡判断InOrder 中序遍历 完整代码 概念 红黑树&#xff0c;是一种二叉搜索树&…