定位和分析解决std::thread创建失败的问题和解决方法(mmap虚拟地址耗尽)

文章目录

    • 引言
    • 问题描述和分析
      • 监控shell脚本
      • shell脚本解释
    • 问题根源追溯
    • 解决方案一:增大mmap区域
    • 解决方案二:优化线程栈空间
    • 解决方案三:引入线程池
    • 参考文章

引言

在高并发和长周期运行的环境中,频繁创建std::thread线程可能导致mmap虚拟地址空间耗尽,进而引发资源不足的错误。
本文提出的增大mmap区域、优化线程栈空间以及引入线程池的策略,能够有效地管理线程资源,提高应用的稳定性和效率。

问题描述和分析

为处理一些异步任务请求,我们频繁创建std::thread线程来执行任务。尽管初期运行顺利,但随时间推移,后面会遇到“Resource temporarily unavailable”的异常,直接影响了系统的响应时间和整体稳定性。
为精确诊断,我们设计了一套监控机制,实时捕捉并记录所有线程状态变化,同时对core dump进行深入解析,以识别故障线程。分析结果表明,问题源于std::thread线程创建阶段,具体表现为EAGAIN错误——指示系统资源暂时不可用。

监控shell脚本

iterate_threads_info() {local pids=$(pidof "$1")local output_file="$1.info"# Write header to output file{echo "Record start: $(date)"echo "--------------------------------------------------------------"} >> "$output_file"# Iterate over each process and its threadsfor pid in $pids; doecho "Process $pid" >> "$output_file"cat /proc/$pid/maps >> "$output_file"for tid in /proc/$pid/task/*; dotid=$(basename "$tid"){echo "--------------------------------------------------------------"echo "Thread: $tid"echo "--------------------------------------------------------------"echo "status:"cat /proc/$pid/task/$tid/statusecho "stack:"cat /proc/$pid/task/$tid/stackecho "syscall:"cat /proc/$pid/task/$tid/syscallecho ""} >> "$output_file"doneecho "" >> "$output_file"done
}

shell脚本解释

  • 参数 $1: 这个参数是函数 iterate_threads_info 的输入参数,它表示要查询的进程的名称。在脚本执行时,你会把要查询的进程的名称作为脚本的第一个参数传递给这个函数。

    local pids=$(pidof "$1")
    local output_file="$1.info"
    
    • pidof "$1":使用 pidof 命令获取指定进程名称(由 $1 提供)对应的进程ID(PID)。这些PID将存储在 pids 变量中。
    • "$1.info":构造一个输出文件名,使用传递给函数的进程名称 $1 加上 .info 后缀。这个文件名用于存储进程及其线程的详细信息。
  • 输出文件: output_file 变量用来存储输出文件的名称,在函数执行时会根据传递给函数的进程名称动态生成。

  • 循环遍历进程和线程:

    • 首先,对于每一个通过 pidof "$1" 获取的进程ID,脚本会将进程ID打印到输出文件中,并输出该进程的 maps 文件内容。
    • 然后,使用 for tid in /proc/$pid/task/* 遍历该进程的所有线程(/proc/$pid/task/ 下的所有文件和目录),其中 $pid 是当前进程的PID。
    • 对于每个线程,输出它的 statusstacksyscall 文件内容到输出文件中,以及相关的分隔符和空行用于格式化输出。

问题根源追溯

进一步探究线程创建流程,从C++标准库std::thread出发,经由POSIX线程API pthread_create,直至内核层面的clonedo_fork函数。核心发现:内核在尝试分配新线程所需mmap区域时,因虚拟地址空间不足,触发了EAGAIN错误。

解决方案一:增大mmap区域

针对虚拟地址空间不足的问题,我们通过修改内核参数来增大mmap区域。默认情况下,TASK_UNMAPPED_BASE的值为TASK_SIZE / 3,这个值大约是进程虚拟地址空间的1/3,系统通常会将大部分的虚拟地址空间分配给已映射的区域(如代码段、堆、栈等),只留少量空间给未映射区域。

我们将TASK_UNMAPPED_BASE的值从默认的0x2AAA8000调整至0x10000000。这一调整实际上是将未映射区域的起始地址向高地址移动,扩展了系统的虚拟地址空间中可供动态分配的内存空间。重启服务后,线程创建成功率大幅提升,系统运行稳定无阻。

解决方案二:优化线程栈空间

除了调整mmap区域外,优化线程栈空间也是提高资源利用率的有效手段。过大的栈空间预分配可能无意间挤占了宝贵的虚拟地址空间。

临时调整栈空间大小(会话级):

ulimit -s 102400

上述命令可即时将栈空间大小设为100MB,适用于当前会话。

永久调整栈空间大小:
编辑/etc/security/limits.conf,添加如下行:

* soft stack 102400

此设置确保系统长期维持100MB的栈空间大小,防止因分配不当引发的创建失败。
使用C++接口设置创建的线程栈大小

pthread_attr_t attribute;
pthread_t thread;pthread_attr_init(&attribute);
pthread_attr_setstacksize(&attribute, 10240); // 设置线程栈的大小为10K
pthread_create(&thread,&attribute,foo,0);
pthread_join(thread,0);

解决方案三:引入线程池

为从根本上解决频繁线程创建带来的问题,可以采用线程池(Thread Pool)的设计模式。线程池预先创建一组固定数量的工作线程,等待任务到来时再分配给空闲线程执行,而非每次任务都创建新线程。

不仅可以解决mmap虚拟地址空间耗尽的问题,还显著提高了系统性能和资源利用率,使任务执行更加平滑,避免因线程创建失败导致的服务中断,

#ifndef THREAD_POOL_H
#define THREAD_POOL_H#include <mutex>
#include <queue>
#include <thread>
#include <vector>
#include <functional>class ThreadPool {public:explicit ThreadPool(size_t threads);void enqueue(const std::function<void()>& task);~ThreadPool();private:// 线程池配置size_t threads_;// 工作线程std::vector<std::thread> workers_;// 任务队列和同步std::mutex queue_mutex_;std::queue<std::function<void()>> tasks_;bool stop_;
};// 构造函数创建工作线程
inline ThreadPool::ThreadPool(size_t threads) : threads_(threads), stop_(false) {for (size_t i = 0; i < threads_; ++i) {workers_.emplace_back([this] {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lck(queue_mutex_);// 等待任务或停止信号queue_mutex_.wait(lck, [this] { return this->stop_ || !this->tasks_.empty(); });if (this->stop_ && this->tasks_.empty()) {return;}// 获取下一个任务task = std::move(this->tasks_.front());this->tasks_.pop();}// 执行任务task();}});}
}// 将任务排队到线程池中执行
void ThreadPool::enqueue(const std::function<void()>& task) {{std::unique_lock<std::mutex> lck(queue_mutex_);// 停止后不接受任务if (stop_) {throw std::runtime_error("Enqueue on stopped ThreadPool");}// 将任务添加到队列中tasks_.push(task);}queue_mutex_.notify_one();
}// 析构函数等待工作线程终止
inline ThreadPool::~ThreadPool() {{std::unique_lock<std::mutex> lck(queue_mutex_);stop_ = true;}queue_mutex_.notify_all();for (std::thread& worker : workers_) {worker.join();}
}#endif

参考文章

一个std::thread()线程创建失败问题分析过程

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

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

相关文章

设计模式8-桥模式

设计模式8-Bridge 桥模式 由来与目的模式定义结构代码推导1. 类和接口的定义2. 平台实现3. 业务抽象4. 使用示例总结1. 类数量过多&#xff0c;复杂度高2. 代码重复3. 不符合单一职责原则4. 缺乏扩展性改进后的设计1. 抽象和实现分离&#xff08;桥接模式&#xff09;2. 抽象类…

学习XDMA—20240709

概览&#xff1a; 在内部&#xff0c;子系统可以配置为实现多达8个独立的物理DMA引擎(最多4个H2C和4个C2H)。这些DMA引擎可以映射到单独的AXI4Stream接口&#xff0c;也可以将共享的AXI4内存映射(MM)接口映射到用户应用程序。在axis4 MM接口上&#xff0c;PCI Express的DMA/桥接…

智能警卫:Conda包依赖的自动监控之道

智能警卫&#xff1a;Conda包依赖的自动监控之道 引言 在复杂的软件开发项目中&#xff0c;依赖管理是确保项目健康运行的关键环节。Conda作为Python和其他科学计算语言的强大包管理器&#xff0c;提供了依赖监控功能&#xff0c;帮助用户自动化和简化依赖项的监控过程。本文…

软考高级第四版备考--第15天(建设团队)Develop Team

定义&#xff1a;提高工作能力&#xff0c;促进团队成员互动&#xff0c;改善团队整体氛围以提高项目绩效的过程 作用&#xff1a;改进团队协作、增强人际关系技能、激励员工、减少摩擦以提升整体项目绩效 说明&#xff1a;高效团队行为&#xff1a; 使用开放与有效的沟通&a…

简述 JS 中对象的创建和拷贝

在 JavaScript 中&#xff0c;对象是一种非常重要且灵活的数据结构&#xff0c;用于存储多个值&#xff08;属性&#xff09;和方法&#xff08;函数&#xff09; 对象的创建和拷贝是日常开发中经常涉及的操作&#xff0c;对于业务逻辑的准确实现有着重要的作用 本文将简要概…

linux查看目录下的文件夹命令,find 查找某个目录,但是不包括这个目录本身?

linux查看目录下的文件夹命令&#xff0c;find 查找某个目录&#xff0c;但是不包括这个目录本身&#xff1f; Linux中查看目录下的文件夹的命令是使用ls命令。ls命令用于列出指定目录中的文件和文件夹。通过不同的选项可以实现显示详细信息、按照不同的排序方式以及使用不同的…

Profibus转ModbusTCP网关模块实现Profibus_DP向ModbusTCP转换

Profibus和ModbusTCP是工业控制自动化常用的二种通信协议。Profibus是一种串口通信协议&#xff0c;它提供了迅速靠谱的数据传输和各种拓扑结构&#xff0c;如总线和星型构造。Profibus可以和感应器、执行器、PLC等各类设备进行通信。 ModbusTCP是一种基于TCP/IP协议的通信协议…

一次零基础 自“信息收集“到“权限维持“的渗透测试全程详细记录

一、渗透总流程 1.确定目标&#xff1a; 在本靶场中&#xff0c;确定目标就是使用各种扫描工具进行ip扫描&#xff0c;确定目标ip。 2.信息收集&#xff1a; 比如平常挖洞使用fofa&#xff0c;天眼查&#xff0c;ip域名等进行查&#xff0c;在我们这个靶场中比如使用Wappalyz…

基于网络编码的 tcp 变种-tcp/nc

tcp/nc 是指 “tcp with network coding”&#xff0c;是一种结合了网络编码技术的 tcp 变种&#xff0c;网上资源很少&#xff0c;我也不准备多介绍&#xff0c;只介绍它的核心。 传统 tcp 在演进过程中一直搞不定效率问题&#xff0c;网络带宽在增长&#xff0c;cpu 却没有变…

C++类和对象(上篇)

文章目录 前言一、面向过程和面向对象初步认识 二、类的引入 三、类的定义 六、类的实例化 七、类的对象大小的计算 八、类成员函数的this指针 总结 前言 类和对象是面向对象编程的两个核心概念。 类是一种抽象的数据类型&#xff0c;是描述对象共同特征和行为的模板。一个类…

yolov5:Conv类参数量计算

Conv是yolov5自定义的类&#xff0c;里边包含了卷积层、BN层和激活函数 class Conv(nn.Module):# Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)default_act nn.SiLU() # default activationdef __init__(self, c…

点云下采样有损压缩

转自本人博客&#xff1a;点云下采样有损压缩 点云下采样是通过一定规则对原点云数据进行再采样&#xff0c;减少点云个数&#xff0c;降低点云稀疏程度&#xff0c;减小点云数据大小。 1. 体素下采样&#xff08;Voxel Down Sample&#xff09; std::shared_ptr<PointClo…

华为机考真题 -- 信道分配

题目描述&#xff1a; 算法工程师小明面对着这样一个问题&#xff0c;需要将通信用的信道分配给尽量多的用户&#xff0c; 信道的条件及分配规则如下&#xff1a; 1) 所有信道都有属性&#xff1a;”阶”。阶为 r 的信道容量为 2^r 比特&#xff1b; 2) 所有用户需要传输的数…

区间贪心

目录 1.贪心算法的思想 2.区间贪心算法常用的一些题目类型 1.选择最多不相交区间问题 P2970 [USACO09DEC] Selfish Grazing S 1.思路分析 2.上代码 2.区间选点问题 P1250 种树 1.题目 2.方法一 1.代码解释 3.方法二 3.区间合并问题 P2434 [SDOI2005] 区间 1. 思路…

中科海讯 C++初级研发工程师笔试题目

C语言中的const关键字有什么作用&#xff1f;为什么要使用const关键字&#xff1f; 1 const修饰的变量将会被放到常量区&#xff0c;避免被意外的改动。 const修饰的常量比#define修饰的有更多的优势&#xff0c;比如可以调试&#xff0c;类型检查等 2 const修饰的参数可做输入…

Java集合面试题

Java集合框架 1、List、Set、Map的区别2、ArrayList、LinkedList、Vector区别3、为什么数组索引从0开始&#xff0c;而不是从1开始&#xff1f;4、ArrayList底层的实现原理5、红黑树、散列表6、HashMap的底层原理7、HashMap的put方法具体流程8、HashMap的扩容机制9、HashMap是怎…

南方科技大学马永胜教授给年轻人使用AI工具上的建议

摘要 - 1. AI的未来&#xff0c;是机器人和机器人之间的合作&#xff1b; 2. 行业的发展方向是需求决定的&#xff0c;不要做同质化的发展&#xff0c;要做专/精/特/新&#xff1b; 3. 新质生产力 &#xff08; 科学技术革命性突破 生产要素创新型配置 产业深度转型升级&…

java通过poi-tl导出word实战详细步骤

文章目录 与其他模版引擎对比1.引入maven依赖包2.新建Word文档exportWprd.docx模版3.编写导出word接口代码4.导出成果 poi-tl是一个基于Apache POI的Word模板引擎&#xff0c;也是一个免费开源的Java类库&#xff0c;你可以非常方便的加入到你的项目中&#xff0c;并且拥有着让…

贪心算法-以高校教材管理系统为例

1.贪心算法介绍 1.算法思路 贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行&#xff0c;根据某个优化测度&#xff0c;每一 步都要确保能获得局部最优解。每一步只考虑一 个数据&#xff0c;其选取应该满足局部优化的条件。若下 一个数据和部分最优解连在一起…

Pix4Dmapper:无人机测绘的革命性工具

在现代测绘和地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;Pix4Dmapper无疑是一款革命性的工具。作为一名长期使用这款软件的用户&#xff0c;我深深感受到它在工作中的重要性和便利性。Pix4Dmapper不仅仅是一款软件&#xff0c;更是测绘工作者的得力助手&#xff…