当前位置: 首页 > news >正文

cgroup threaded功能例子

一、背景

cgroup在如今的系统里基本都是默认打开的一个功能。对于cgroup的cpu子系统,默认的颗粒度是进程为维度进行cgroup的cpu及cpuset的控制。而对于一些复杂进程,可能的需求是进程里一些个别线程要绑定在X1-Xn这些cpu核上,而除了这些个别线程之外的其他线程都需要绑定在Y1-Yn这些cpu核上。对于进程颗粒度的需求,一般来说systemd的能力就覆盖到了,但是对于这种相对复杂的线程颗粒度的需求,我们其实可以有几种处理方法。比如:

1)通过cpulimit进行CPU限制

——这种方式不需要root权限,原理是通过周期性地暂停和恢复进程(发送SIGSTOP和SIGCONT信号)来间接限制CPU限制,这种方式相对不太正式,属于“调试性”的限制方案。

2)通过管理服务或线程自己调用api进行线程级别的绑核控制

——在进程完全启动之后,通过绑核的api(sched_setaffinity)进行绑核设置,这种方式管理方式只能控制绑核,控不了cpu限额。我们倒是可以用systemd的进程限额能力配合这个管理服务的线程绑核能力来缝合成一个需求覆盖得相对全的功能。

3)通过cgroup的threaded功能进行分层的管理

——cgroup的threaded功能可以支持线程颗粒度的绑核和限额的控制,但是需要遵循一定的cgroup组层次结构的要求。另外这一个threaded功能目前还没有融进systemd里,这个控制需要另外的服务来做,当然另外的服务需要考虑当前系统里的要管控的进程的systemd的配置,这是需要一定的细节的。

这篇博客展开的是第三种方法,这种方法由于借助的是cgroup的能力,所以它对线程颗粒度的控制更加强大,为什么这么说,因为cgroup的设置一旦生效后,后续的非法的sched_setaffinity的设置都会被忽略,因为cgroup是一个强制的内核级别的管控功能,其他方式都需要遵照这样的规则。

下面第二章里,我们贴出测试程序源码,并进行成果展示,在第三章里,我们展开相关功能的一些细节。

二、测试源码及成果展示

2.1 测试源码

#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <cstdlib>
#include <fstream>
#include <unistd.h>
#include <sys/syscall.h>const int THREAD_COUNT = 200;
const int RUN_TIME_US = 100; // 100 us
const std::string CGROUP_NAME = "test/my_cgroup";// 获取线程的 TID
pid_t gettid() {return static_cast<pid_t>(syscall(SYS_gettid));
}void threadFunction(const std::string& cgroup_path) {// 将当前线程的 TID 写入 cgroup.threadsstd::ofstream cgroup_threads(cgroup_path + "/cgroup.threads", std::ios::app);if (cgroup_threads.is_open()) {cgroup_threads << gettid() << std::endl; // 写入当前线程的 TIDcgroup_threads.close();}while (true) { // 无限循环,您可以根据需要添加退出条件// 获取当前时间auto start_time = std::chrono::high_resolution_clock::now();// 进行 100 微秒的死循环auto end_time = start_time + std::chrono::microseconds(RUN_TIME_US);while (std::chrono::high_resolution_clock::now() < end_time) {// 当前循环只是占用CPU}// 计算剩余的时间并休眠auto sleep_duration = std::chrono::milliseconds(100) - (std::chrono::high_resolution_clock::now() - start_time);if (sleep_duration > std::chrono::milliseconds(0)) {std::this_thread::sleep_for(sleep_duration); // 休眠剩余时间}}
}void threadFunctionEx(const std::string& cgroup_path) {// 将当前线程的 TID 写入 cgroup.threadsstd::ofstream cgroup_threads(cgroup_path + "/cgroup.threads", std::ios::app);if (cgroup_threads.is_open()) {cgroup_threads << gettid() << std::endl; // 写入当前线程的 TIDcgroup_threads.close();}std::this_thread::sleep_for(std::chrono::seconds(1));std::string command = "/usr/bin/deadloop &";system(command.c_str());while (true) { // 无限循环,您可以根据需要添加退出条件// 获取当前时间auto start_time = std::chrono::high_resolution_clock::now();// 进行 100 微秒的死循环auto end_time = start_time + std::chrono::microseconds(RUN_TIME_US);while (std::chrono::high_resolution_clock::now() < end_time) {// 当前循环只是占用CPU}// 计算剩余的时间并休眠auto sleep_duration = std::chrono::milliseconds(100) - (std::chrono::high_resolution_clock::now() - start_time);if (sleep_duration > std::chrono::milliseconds(0)) {std::this_thread::sleep_for(sleep_duration); // 休眠剩余时间}}
}void createCgroup(std::string& cgroup_path) {// 创建 cgroup 目录cgroup_path = "/sys/fs/cgroup/" + CGROUP_NAME;std::string command = "mkdir -p " + cgroup_path;system(command.c_str());// 设置 cgroup 为线程模式std::ofstream cgroup_mode(cgroup_path + "/cgroup.type");if (cgroup_mode.is_open()) {cgroup_mode << "threaded" << std::endl; // 设置为线程模式cgroup_mode.close();}// 将当前进程的 PID 写入 cgroup.procsstd::ofstream cgroup_cpus(cgroup_path + "/cpuset.cpus");if (cgroup_cpus.is_open()) {cgroup_cpus << 30;cgroup_cpus.close();}// 设置 CPU 限制std::ofstream cpu_max(cgroup_path + "/cpu.max");if (cpu_max.is_open()) {cpu_max << "15000 100000" << std::endl; // 设置 CPU 限制cpu_max.close();}
}int main() {std::string cgroup_path;cgroup_path = "/sys/fs/cgroup/test";std::string command = "mkdir -p " + cgroup_path;system(command.c_str());command = "echo " + std::to_string(getpid()) + " >" + cgroup_path + "/cgroup.procs";system(command.c_str());command = "echo +cpuset +cpu > " + cgroup_path + "/cgroup.subtree_control";system(command.c_str());// 创建 cgroupcreateCgroup(cgroup_path);// 创建线程std::vector<std::thread> threads;for (int i = 0; i < THREAD_COUNT; ++i) {threads.emplace_back(threadFunction, cgroup_path);}threads.emplace_back(threadFunctionEx, cgroup_path);// 主线程等待所有子线程完成for (auto& thread : threads) {thread.join();}// 清理 cgroupcommand = "rmdir " + cgroup_path; // 清理 cgroup 目录system(command.c_str());return 0;
}

2.2 成果展示

运行 2.1 源码编出的程序后,通过如下命令查看所在的cgroup组:

cat /proc/2808174/cgroup

看到主线程所在的cgroup组是/sys/fs/cgroup/test的这个cgroup组,我们看一下其他线程所在的cgroup组:

可以看到其他线程都是在/sys/fs/cgroup/test/my_cgroup这个cgroup组下的,我们看一下test和test/my_cgroup这两个cgroup组的当前模式:

可以看到test这个cgroup组的模式是domain threaded,test/my_cgroup这个cgroup组的模式是threaded。

另外,可以从下图看到,代码里创建的一个死循环程序,属于该testcgroupthread进程里一个线程的子进程:

如上图,是先把当前线程加入到threaded模式的组里,再进行的fork来创建deadloop程序。

这时候,该deadloop程序还是属于当前这个threaded模式的组里的:

关于这个父子进程的cgroup实验的更多细节见之前的博客 cgroup父子进程的加组实验_linux cgroup例子-CSDN博客。

三、细节展开

3.1 若要把某个线程加入到threaded类型的子组里,需要先把该线程加入到threaded类型子组的父组里

下图的命令和命令执行情况可以清楚的看到标题的这个结论:

若要把某个线程加入到threaded类型的子组里,需要先把该线程加入到threaded类型子组的父组里

3.2 若把一个进程里的某个线程调整到某个domain的cgroup组里,该进程上的所有线程都会被调整到该cgroup组里

下图的命令和命令执行情况可以清楚的看到标题的这个结论:

若把一个进程里的某个线程调整到某个domain的cgroup组里,该进程上的所有线程都会被调整到该cgroup组里

如上图,左边的terminal里是启动了 2.1 一节源码里的程序,它加入的是/sys/fs/cgroup/test这个cgroup组里,但是如上图里右边截图,一旦我们调整了其中一个线程echo到/sys/fs/cgroup/cgroup.procs里后,该进程上的所有线程都被一同拉过去了。

3.3 若domain的type的cgroup组内创建了一个threaded类型的子组后,父组的模式就从domain变成了domain threaded

下图的命令和命令执行情况可以清楚的看到标题的这个结论:

若domain的type的cgroup组内创建了一个threaded类型的子组后,父组的模式就从domain变成了domain threaded。

相关文章:

  • 工厂模式:简单工厂模式
  • 使用纯前端技术html+css+js实现一个蔬果商城的前端模板!
  • 【LeetCode】1.两数之和
  • 重新定义户外防护!基于DeepSeek的智能展开伞棚系统技术深度解析
  • Cpp实现window上cmd执行效果
  • 2025.4.21日学习笔记 JavaScript String、Array、date、math方法的使用
  • linux基础学习--linux文件与目录管理
  • 目标检测篇---Fast R-CNN
  • 四元数转旋转矩阵
  • 第一篇:从哲学到管理——实践论与矛盾论如何重塑企业思维
  • Java高频面试之并发编程-04
  • 瑞吉外卖-分页功能开发中的两个问题
  • Python爬虫实战:获取高考网专业数据并分析,为志愿填报做参考
  • 【Python爬虫实战篇】--爬取豆瓣电影信息(静态网页)
  • 【Python网络爬虫开发】从基础到实战的完整指南
  • 算法之动态规划
  • 【Unity iOS打包】报错解决记录
  • 34、Spark实现读取XLS文件
  • Linux 进程与线程间通信方式及应用分析
  • 什么是Manus,国内用户如何订阅Manus
  • 西安雁塔区委书记王征拟任市领导班子副职,曾从浙江跨省调任陕西
  • 商务部:试点示范已形成9批190多项创新成果向全国推广
  • “解压方程式”主题沙龙:用艺术、精油与自然的力量,寻找自我疗愈的方式
  • 罗马教皇方济各去世
  • “我们一直都是面向全世界做生意”,“世界超市”义乌一线走访见闻
  • 俄官员称乌克兰未遵守停火,乌方暂无回应