Linux下进程的CPU配置与线程绑定过程

2025-07-11 18:50

本文主要是介绍Linux下进程的CPU配置与线程绑定过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配...

1 基于进程的CPU配置

基于进程的CPU配置技术是指在linux操作系统中,通过调整进程的CPU使用率来实现对系统资源的合理分配和管理。这种技术可以用于限制特定进程的CPU使用,防止其占用过多的CPU资源,从而保证系统的稳定性和性能。

基于进程的CPU配置是指将一个进程绑定到特定的CPU核心或者CPU集合上运行。这样可以控制进程在特定的CPU资源上执行,以提高性能或实现特定的调度策略。

1.1 对CPU亲和力的配置

对于Centos 8下的Linux系统,首先可以使用top命令来查看当前的CPU占用率,如下图所示:

键盘上按数字1,可以以数据化的形式看到具体的使用情况,如下图:

Linux下进程的CPU配置与线程绑定过程

键盘上按字母t,可以以图形化的形式看到具体的使用情况,如下图:

Linux下进程的CPU配置与线程绑定过程

Linux下进程的CPU配置与线程绑定过程

可以看到,当前的CPpythonU基本是空闲的状态。

这时候,我们写一个测试代码,让其挂在后台运行,例如下面的一个死循环函数:

#include <IOStream>

int main()
{
    while (true) {
        // 在这里编写你的代码
        // 例如,输出一条信息
        std::cout << "Hello, I am running in an infinite loop!" << std::endl;
    }

    return 0;
}

让其挂在后台运行着,暂时不管。

如下图:

Linux下进程的CPU配置与线程绑定过程

Linux下进程的CPU配置与线程绑定过程

此时,我们再次使用top命令,来查看当前的CPU 状态,如下图:

Linux下进程的CPU配置与线程绑定过程

可以看到,此时循环代码这个进程正在后台不停运行,此时的CPU占用率相比开始,非常的高。

记住当前的进程PID,然后使用taskset -c -p 13265 命令查看当前进程的CPU亲和力。13265是当前进程的PID。

Linux下进程的CPU配置与线程绑定过程

可以从输出的信息看出,pid 为13265的进程(即当前的测试进程)的亲和力CPU为0-3,即它同时运行在了0,1,2,3这四个CPU上面。

现在我们更改pid 为13265的进程(即当前的测试进程)的亲和力,将其改成只在0,1这两个CPU上面运行。如下图:

Linux下进程的CPU配置与线程绑定过程

Linux下进程的CPU配置与线程绑定过程

这里由于我使用了ctrl + c暂停了刚才的进程,然后重新启动该进程时,进程的PID变成了13495,不过这没有任何影响。可以看到,当前的进程新的亲和力列表为0,1,说明设置新的CPU亲和力成功。而pid 13495 的当前亲和力掩码为3,同样能说明亲和力设置成功。使用taskset命令来查看进程运行在哪个CPU上。

使用以下命令:

taskset -p < PID >

【请将< PID >替换为你要查看的进程的实际PID。】

该命令将显示进程的当前亲和力掩码,其中每个位表示一个CPU核心。例如,如果输出为pid 's current affinity mask: 3,表示进程当前在CPU核心0和1上运行,因为二进制表示为11。

再次实验一下,这次我们将CPU亲和力设置在CPU 1,CPU2上,如下图:

Linux下进程的CPU配置与线程绑定过程

由当前亲和力掩码可以看到,为6,说明此时运行在CPU1和CPU2上。(2^1 + 2^2 = 6)

1.2 绑定进程到指定CPwww.chinasem.cnU核上运行

查看CPU有几个核

使用 cat /proc/cpuinfo 查看CPU信息,如下两个信息:

  • ·processor:指明第几个cpu处理器
  • ·cpu cores:指明每个处理器的核心数

Linux下进程的CPU配置与线程绑定过程

以本机中虚拟机为例,有4个CPU(分别为:CPU0, CPU1, CPU2, CPU3),每个CPU有1个核。

也可以使用系统调用sysconf获取CPU核心数:

#include <unistd.h>

int sysconf(_SC_NPROCESSORS_CONF);/* 返回系统可以使用的核数,但是其值会包括系统中禁用的核的数目,因 此该值并不代表当前系统中可用的核数 */
int sysconf(_SC_NPROCESSORS_ONLN);/* 返回值真正的代表了系统当前可用的核数 */

/* 以下两个函数与上述类似 */
#include <sys/sysinfo.h>

int get_nprocs_conf (void);/* 可用核数 */
int get_nprocs (void);/* 真正的反映了当前可用核数 */

使用 taskset 指令 

  • 获取进程pid:

Linux下进程的CPU配置与线程绑定过程

  • 查看进程当前运行在哪个CPU上:

Linux下进程的CPU配置与线程绑定过程

显示的十六进制f转换为二进制为最低四个是1,每个1对应一个CPU,所以进程运行在4个CPU上。

  • 指定进程10770运行在CPU0上:

Linux下进程的CPU配置与线程绑定过程

注意,CPU的标号是从0开始的,所以cpu0表示第1个CPU(第一个CPU的标号是0)。

至此,就把应用程序绑定到了CPU0上运行,查看如下:

Linux下进程的CPU配置与线程绑定过程

  • 启动程序时绑定CPU:

例如启动时绑定到第二个CPU上,即CPU1:

Linux下进程的CPU配置与线程绑定过程

使用sched_setaffinity系统调用 

通过系统调用sched_setaffinity进行绑定,通过scOEDbePhed_getaffinity获取绑定关系。注意这对方法是进程级别的绑定。代码中指定cpu0和cpu3,我们可以通过top查看,两个CPU使用达到了100%,其他的CPU均不会(正常场景)。

sched_setaffinity可以将某个进程绑定到一个特定的CPU。
#define _GNU_SOURCE             /* See feature_test_MACros(7) */
#include <sched.h>

/* 设置进程号为pid的进程运行在mask所设定的CPU上
 * 第二个参数cpusetsize是mask所指定的数的长度
 * 通常设定为sizeof(cpu_set_t)

 * 如果pid的值为0,则表示指定的是当前进程 
 */
int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);

int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);/* 获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中 */

代码示例:

/*

*该程序演示了如何使用sched_setaffinity函数将线程绑定到特定的CPU核心上运行。
*程序首先创建了两个线程,然后使用sched_setaffinity函数将线程1绑定到CPU 0上,将线程2绑定到CPU 3上。
*运行时,可以通过查看输出的pid来确定程序的进程ID。
*然后,程序将CPU_ZERO宏应用于一个cpu_set_t类型的变量mask,以将其初始化为空集。
*接下来,程序将CPU_SET宏应用于mask,将CPU 0和CPU 3添加到集合中。
*最后,程序调用sched_setaffinity函数将mask应用于当前进程,将线程1绑定到CPU 0上,将线程2绑定到CPU 3上。
*线程创建成功后,程序使用pthread_join函数等待线程1和线程2的结束。
*/
#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <stdio.h>
#include <sys/typOEDbePes.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
 
void* testfunc(void* t) {
  while(1);
  return NULL; 
}
 
int main()
{
  cpu_set_t mask; // 定义cpu_set_t类型的变量mask,用于存储CPU集合
  printf("pid=%d\n", getpid()); // 打印进程ID
  CPU_ZERO(&mask); // 将mask初始化为空集
  CPU_SET(0, &mask);//将cpu0绑定到mask中
  CPU_SET(3, &mask);//将cpu3绑定到mask中

  // 将mask应用于当前进程,绑定线程到指定的CPU核心
  sched_setaffinity(0, sizeof(cpu_set_t), &mask) ;
  
  pthread_t tid1;//创建线程1
  if (pthread_create(&tid1, NULL, (void *)testfunc, NULL) != 0) 
  {      
    fprintf(stderr, "thread create failed\n"); // 线程创建失败,打印错误信息
    return -1;   
  }
  pthread_t tid2;//创建线程2
  if (pthread_create(&tid2, NULL, (void *)testfunc, NULL) != 0) 
  {      
    fprintf(stderr, "thread create failed\n"); // 线程创建失败,打印错误信息
    return -1;   
  } 
  pthread_join(tid1, NULL); // 等待线程1结束
  pthread_join(tid1, NULL); // 等待线程2结束
  return 0;
}

执行结果如下图所示: 

  • 执行前:

Linux下进程的CPU配置与线程绑定过程

  • 执行后:

Linux下进程的CPU配置与线程绑定过程

2 基于线程的CPU配置

2.1 线程绑定:使用函数pthread_setaffinity_np

线程绑定CPU核心的意义:

  • 在多核CPU中合理的调度线程在各个核上运行可以获得更高的性能。
  • 在多线程编程中,每个线程处理的任务优先级是不一样的,对于要求实时性比较高的线程或者是主线程,对于这种线程我们可以在创建线程时指定其绑定到某个CPU核上,以后这个核就专门处理该线程。
  • 这样可以使得该线程的任务可以得到较快的处理,特别是和用户直接交互的任务,较短的响应时间可以提升用户的体验感。

几个重要的宏操作:

一个线程的CPU亲合力掩码用一个cpu_set_t结构体来表示一个CPU集合,下面的几个宏分别对这个掩码集进行操作:

CPU_ZERO() www.chinasem.cn清空一个集合
CPU_SET()与CPU_CLR()分别对将一个给定的CPU号加到一个集合或者从一个集合中去掉
CPU_ISSET()检查一个CPU号是否在这个集合中

设置获取线程CPU亲和力状态:

sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask)

该函数设置线程为pid的这个线程,让它运行在mask所设定的CPU上。

如果pid的值为0,则表示指定的是当前线程,使当前线程运行在mask所设定的那些CPU上。

第二个参数cpusetsize是mask所指定的数的长度。通常设定为sizeof(cpu_set_t)。

如果当前pid所指定的线程此时没有运行在mask所指定的任意一个CPU上,则该指定的线程会从其它CPU上迁移到mask的指定的一个CPU上运行。

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

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

简单的实例:

// 此代码不完整,只是帮助理解绑定过程
// 在创建线程时添加以下代码,可以将该线程绑定到1核
cpu_set_t mask;

// 将掩码清零
CPU_ZERO(&mask);

// 将1添加到掩码中
CPU_SET(1, &mask);

// #将本线程绑定到1核
sched_setaffinity(0, sizeof(cpu_set_t), &mask);

查看线程是否运行在指定的核上:

实际工作中,为了方便查看线程的情况,会在创建线程时将相关信息保存到一个文件中,需要时用cat命令查看,内容包括创建了哪些线程、线程名称、线程id和pid、绑定的CPU核、优先级、调度方式等。

使用“top”命令查看:

  • top -d 2:查看线程的运行情况和CPU状态
  • 按’h’ 和 1:在上一句的基础上可以查看更详细的信息。

从文件中得到线程pid和ppid,通过top命令,查看线程在哪个CPU核上运行,验证核绑定的核是否一样。

对于线程绑定,我们需要借助pthread库,通过函数pthread_setaffinity_np来设置绑定cpu关系。我们通过top查看,会发现cpu0和cpu3使用率达到100%。

代码实例:

/*
 *该程序演示了如何使用pthread_setaffinity_np函数将线程绑定到特定的CPU核心上运行。
 *程序首先创建了两个线程,然后使用pthread_setaffinity_np函数将线程1绑定到CPU 0上,将线程2绑定到CPU 3上。
 *运行时,可以通过查看输出的pid来确定程序的进程ID。
 *然后,程序将CPU_ZERO宏应用于一个cpu_set_t类型的变量mask,以将其初始化为空集。
 *接下来,程序使用pthread_create函数创建线程1和线程2,并检查线程创建是否成功。
 *然后,程序打印出线程1和线程2的ID。
 *程序使用CPU_SET宏将CPU 0添加到mask中,并使用pthread_setaffinity_np函数将mask应用于线程1,将线程1绑定到CPU 0上。
 *然后,程序清除之前设置的mask,并将CPU 3添加到mask中,并使用pthread_setaffinity_np函数将mask应用于线程2,将线程2绑定到CPU   3上。
 *最后,程序使用pthread_join函数等待线程1和线程2的结束。
*/
#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>

void* testfunc(void* t) {
  int i = 3; // 初始化循环计数器为3
  while(i) { // 进入循环,条件为i非零
     sleep(5); // 休眠5秒
     printf("tid=%d,cpu=%d\n",pthread_self(), sched_getcpu()); // 打印线程ID和CPU编号
     i--; // 计数器减一
  }
  while(1); // 进入无限循环
  return NULL; 
}

int main()
{
  cpu_set_t mask; // 定义CPU集合
  printf("pid=%d\n", getpid()); // 打印进程ID
  CPU_ZERO(&mask); // 清空CPU集合
  
  pthread_t tid1; // 定义线程tid1
  if (pthread_create(&tid1, NULL, (void *)testfunc, NULL) != 0) 
  {      
    fprintf(stderr, "thread create failed\n"); // 线程创建失败,打印错误信息
    return -1;   
  }
  pthread_t tid2; // 定义线程tid2
  if (pthread_create(&tid2, NULL, (void *)testfunc, NULL) != 0) 
  {      
    fprintf(stderr, "thread create failed\n"); // 线程创建失败,打印错误信息
    return -1;   
  } 
  printf("tid1=%d,tid2=%d\n", tid1,tid2); // 打印线程tid1和tid2的值
 
  CPU_SET(0, &mask); // 将CPU0加入CPU集合
  pthread_setaffinity_np(tid1, sizeof(cpu_set_t), &mask) ; // 设置线程tid1的CPU亲和性为CPU0
  
  // 清除之前设置,重新设置绑定cpu3
  CPU_ZERO(&mask); // 清空CPU集合
  CPU_SET(3, &mask); // 将CPU3加入CPU集合
  pthread_setaffinity_np(tid2, sizeof(cpu_set_t), &mask) ; // 设置线程tid2的CPU亲和性为CPU3
  
  pthread_join(tid1, NULL); // 等待线程tid1结束
  pthread_join(tid1, NULL); // 等待线程tid2结束
  return 0;
}
  • 执行之后:

Linux下进程的CPU配置与线程绑定过程

  • 将其kill,恢复:

Linux下进程的CPU配置与线程绑定过程

建议:进行配置之前先将虚拟机拍摄快照,以防配置不当出现意外情况。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于Linux下进程的CPU配置与线程绑定过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155359

相关文章

QT Creator配置Kit的实现示例

《QTCreator配置Kit的实现示例》本文主要介绍了使用Qt5.12.12与VS2022时,因MSVC编译器版本不匹配及WindowsSDK缺失导致配置错误的问题解决,感兴趣的可以了解一下... 目录0、背景:qt5.12.12+vs2022一、症状:二、原因:(可以跳过,直奔后面的解决方法)三、解决方

Java中如何正确的停掉线程

《Java中如何正确的停掉线程》Java通过interrupt()通知线程停止而非强制,确保线程自主处理中断,避免数据损坏,线程池的shutdown()等待任务完成,shutdownNow()强制中断... 目录为什么不强制停止为什么 Java 不提供强制停止线程的能力呢?如何用interrupt停止线程s

SpringBoot路径映射配置的实现步骤

《SpringBoot路径映射配置的实现步骤》本文介绍了如何在SpringBoot项目中配置路径映射,使得除static目录外的资源可被访问,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一... 目录SpringBoot路径映射补:springboot 配置虚拟路径映射 @RequestMapp

Java Kafka消费者实现过程

《JavaKafka消费者实现过程》Kafka消费者通过KafkaConsumer类实现,核心机制包括偏移量管理、消费者组协调、批量拉取消息及多线程处理,手动提交offset确保数据可靠性,自动提交... 目录基础KafkaConsumer类分析关键代码与核心算法2.1 订阅与分区分配2.2 拉取消息2.3

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

python 线程池顺序执行的方法实现

《python线程池顺序执行的方法实现》在Python中,线程池默认是并发执行任务的,但若需要实现任务的顺序执行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录方案一:强制单线程(伪顺序执行)方案二:按提交顺序获取结果方案三:任务间依赖控制方案四:队列顺序消

Nginx中配置使用非默认80端口进行服务的完整指南

《Nginx中配置使用非默认80端口进行服务的完整指南》在实际生产环境中,我们经常需要将Nginx配置在其他端口上运行,本文将详细介绍如何在Nginx中配置使用非默认端口进行服务,希望对大家有所帮助... 目录一、为什么需要使用非默认端口二、配置Nginx使用非默认端口的基本方法2.1 修改listen指令

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

SysMain服务可以关吗? 解决SysMain服务导致的高CPU使用率问题

《SysMain服务可以关吗?解决SysMain服务导致的高CPU使用率问题》SysMain服务是超级预读取,该服务会记录您打开应用程序的模式,并预先将它们加载到内存中以节省时间,但它可能占用大量... 在使用电脑的过程中,CPU使用率居高不下是许多用户都遇到过的问题,其中名为SysMain的服务往往是罪魁