C/C++线程绑核详解

news2024/12/29 9:07:49

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

一:为什么需要绑核

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

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 command

2,绑定一个进程到多个 CPU 核心
taskset -c <core_id_list> <command>
例:进程绑定到第 1、2、4 个 CPU 核心上
taskset -c 0,1,3 command

3,显示一个进程当前的 CPU 亲和度:
taskset -p <pid>

4,修改一个正在运行的进程的 CPU 亲和度:
taskset -cp <core_id_list> <pid>
例:将进程 12345 绑定到第 1、2、4 个 CPU 核心上
taskset -cp 0,1,3 12345

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/605879.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

16.3:岛屿数量问题2

岛屿数量问题2 https://leetcode.cn/problems/number-of-islands-ii/ 给你一个大小为 m x n 的二进制网格 grid 。网格表示一个地图&#xff0c;其中&#xff0c;0 表示水&#xff0c;1 表示陆地。最初&#xff0c;grid 中的所有单元格都是水单元格&#xff08;即&#xff0c…

Dubbo源码解析一网络通信原理

Dubbo 网络通信原理 1. Dubbo高可用集群1.1 服务集群的概述1.1.1 服务集群的概述1.1.2 调用过程1.1.3 组件介绍 1.2 集群容错机制1.2.1 内置集群容错策略1.2.1.1 Failover(失败自动切换)1.2.1.2 Failsafe(失败安全)1.2.1.3 Failfast(快速失败)1.2.1.4 Failback(失败自动恢复)1.…

卡尔曼滤波 | Matlab实现利用卡尔曼滤波器估计电池充电状态(Kalman Filtering)

文章目录 效果一览文章概述研究内容程序设计参考资料效果一览 文章概述 卡尔曼滤波 | Matlab实现利用卡尔曼滤波器估计电池充电状态(Kalman Filtering) 研究内容

gyp verb `which` failed Error: not found: python2

安装node-sass居然需要python2,7环境&#xff0c;不能python3 我只能重新降版本&#xff1a; python2.7:https://www.python.org/ftp/python/2.7/python-2.7.amd64.msi npm ERR! code 1 npm ERR! path F:\idea2021work\music01 初始化\music-client\node_modules\node-sass np…

自然语言处理从入门到应用——自然语言处理的基础任务:词性标注(POS Tagging)和句法分析(Syntactic Parsing)

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 词性标注 词性是词语在句子中扮演的语法角色&#xff0c;也被称为词类&#xff08;Part-Of-Speech&#xff0c;POS&#xff09;。例如&#xff0c;表示抽象或具体事物名字&#xff08;如“计算机”&#xff09;的词被…

【遗传算法简介】

遗传算法&#xff1a;原理与实战 简介 遗传算法是一种模拟达尔文生物进化论的自然选择以及遗传学机制的搜索算法&#xff0c;由 John Holland 在20世纪70年代提出。它们在各种搜索、优化和机器学习任务中已被广泛应用。 遗传算法原理 1. 编码 遗传算法的第一步是将问题的可…

Andriod开发 Room 数据库处理框架

1.Room框架 Room是Android Jetpack组件库中的一部分&#xff0c;它是一个SQLite数据库的抽象层&#xff0c;提供了更简单的API和更好的性能&#xff0c;适合于中大型应用程序。 2.Room的使用 使用Room和之前使用SQLite搭建数据库的过程类似&#xff0c;但是更加简单了。 1&…

JAVA网络编程(一)

一、什么是网络编程 定义&#xff1a;在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输。 应用场景&#xff1a;即时通信&#xff0c;网游&#xff0c;邮件等 不管什么场景&#xff0c;都是计算机与计算机之间通过网络在进行数据传输 java提供一…

软件测试必会:cookie、session和token的区别

今天就来说说session、cookie、token这三者之间的关系&#xff01;最近这仨玩意搞得头有点大&#x1f923; 01、为什么会有它们三个 我们都知道 HTTP 协议是无状态的&#xff0c;所谓的无状态就是客户端每次想要与服务端通信&#xff0c;都必须重新与服务端链接&#xff0c;意…

穿针引线之 AsyncLocalStorage

在 Node.js 中&#xff0c;如何更优雅地获取请求上下文一直是一个问题&#xff0c;看一下下面的例子。 背景 const http require(http); function handler1(req, res) {console.log(req.url); }function handler2(req, res) {console.log(req.url); }http.createServer((req…

【react全家桶】react-Hook (下)

本人大二学生一枚&#xff0c;热爱前端&#xff0c;欢迎来交流学习哦&#xff0c;一起来学习吧。 <专栏推荐> &#x1f525;&#xff1a;js专栏 &#x1f525;&#xff1a;vue专栏 &#x1f525;&#xff1a;react专栏 文章目录 15【react-Hook &#xff08;下&#x…

进程控制(Linux)

进程控制 fork 在Linux中&#xff0c;fork函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 返回值&#xff1a; 在子进程中返回0&#xff0c;父进程中返回子进程的PID&#xff0c;子进程创建失败返回-1。 …

Spring - BeanFactory与ApplicationContext介绍

文章目录 Spring Bean一、BeanFactory 快速入门1.1 BeanFactory 开发步骤1.2 DI依赖注入 二、ApplicationContext快速入门2.1 入门2.2 BeanFactory 与 ApplicationContext关系2.3 BeanFactory 继承体系2.4 ApplicationContext 继承体系 Spring Bean 之前也了解过Spring Bean&a…

高斯过程回归 | Matlab实现高斯过程回归多输入单输出预测(Gaussian Process Regression)

文章目录 效果一览文章概述研究内容程序设计参考资料效果一览 文章概述 高斯过程回归 | Matlab实现高斯过程回归多输入单输出预测(Gaussian Process Regression) 研究内容 高斯过程回归(Gaussian Process Regression,GPR)是一种基于概率模型的非参数回归方法,可以用于

mybatisplus数据权限插件学习初探 动态表名更换插件

文章目录 学习链接 mybatisplus数据权限插件学习初探前言案例建表用户表订单表 环境准备UserUserMapperUserMapper.xmlOrdersOrdersMapperOrdersMapper.xml 配置UserTypeEnumUserContextHolderCustomizeDataPermissionHandlerMybatisPlusConfig 测试测试类bossdeptManagerclerk…

Zinx框架学习 - 消息封装

Zinx - V0.5 消息封装 之前我们使用Request来保存服务器的数据&#xff0c;很显然使用[]byte来接收数据&#xff0c;没有长度也没有消息类型&#xff0c;接下来就要针对这个消息进行封装 创建消息类型 定义一个基本的message包&#xff0c;会包含消息ID、数据、数据长度三个…

路径规划算法:基于探路者优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法…

涉及float和double

文章目录 涉及float和double的问题&#xff1a;它们的存储方式&#xff1a;有效位&#xff1f; 链式结构 涉及float和double的问题&#xff1a; 它们的存储方式&#xff1a; 它们会分成小数部分和指数部分分别存储。小数部分的有效位数越多&#xff0c;精度就越高&#xff0c;…

NLP超详细新手快速入门上手篇(1)常用函数

前言 自然语言处理(NLP)是机器学习的应用之一&#xff0c;用于分析、理解和生成自然语言&#xff0c;以便人类与计算机&#xff0c;人类与人类更好的交流。自然语言处理按照任务类型可以分为分类、匹配、翻译、结构化预测、与序贯决策过程这五类。 本篇参考自TensorFlow官方文…

MyBatis 查询数据库

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 MyBatis 是什么&#xff1f;第⼀个MyBatis查询创建数据库和表添加MyBatis框架支持设置 MyBatis 配置信息添加业务代码 查询操…