Golang http请求忘记调用resp.Body.Close()而导致的协程泄漏问题(含面试常见协程泄漏相关测试题)
参考:
- 知乎:别因为忘记close你的httpclient,造成goroutine泄漏
- CSDN:resp.Body.Close() 引发的内存泄漏goroutine个数
先来看几道题,想一想最终的输出结果是多少呢?
package mainimport ("fmt""io/ioutil""net/http""runtime"
)// 测试1:不执行resp.Body.Close(),也不执行ioutil.ReadAll(resp.Body)
func testFun1() {for i := 0; i < 5; i++ {_, err := http.Get("https://www.baidu.com")if err != nil {fmt.Println("testFun1 http.Get err: ", err)return}}time.Sleep(time.Second * 1)fmt.Println("testFun1() 当前goroutine数量=", runtime.NumGoroutine())
}// 测试2:执行resp.Body.Close(),不执行ioutil.ReadAll(resp.Body)
func testFun2() {for i := 0; i < 5; i++ {resp, err := http.Get("https://www.baidu.com")if err != nil {fmt.Println("testFun2 http.Get err: ", err)return}resp.Body.Close() // 执行resp.Body.Close()}// Close()过程需要一定时间,如果直接输出goroutine数量,可能出现连接还未完全回收的情况,结果有时为1有时为3// 因此,为了结果的准确性,我们这里休眠等待1秒,使得连接完全被回收。time.Sleep(time.Second * 1)fmt.Println("testFun2() 当前goroutine数量=", runtime.NumGoroutine())
}// 测试3:不执行resp.Body.Close(),执行ioutil.ReadAll(resp.Body)
func testFun3() {for i := 0; i < 5; i++ {resp, err := http.Get("https://www.baidu.com")if err != nil {fmt.Println("testFun3 http.Get err: ", err)return}_, _ = ioutil.ReadAll(resp.Body) // 执行ioutil.ReadAll(resp.Body)}time.Sleep(time.Second * 1)fmt.Println("testFun3() 当前goroutine数量=", runtime.NumGoroutine())
}// 测试4:执行resp.Body.Close(),也执行ioutil.ReadAll(resp.Body)
func testFun4() {for i := 0; i < 5; i++ {resp, err := http.Get("https://www.baidu.com")if err != nil {fmt.Println("testFun4 http.Get err: ", err)return}_, _ = ioutil.ReadAll(resp.Body) // 执行ioutil.ReadAll(resp.Body)resp.Body.Close() // 执行resp.Body.Close()}time.Sleep(time.Second * 1)fmt.Println("testFun4() 当前goroutine数量=", runtime.NumGoroutine())
}func main() {testFun1()testFun2()testFun3()testFun4()
}
答案:
- testFun1() 当前goroutine数量= 11
- testFun2() 当前goroutine数量= 1
- testFun3() 当前goroutine数量= 3
- testFun4() 当前goroutine数量= 3
注意:
针对以上testFun2(),如果仅仅执行了 resp.Body.Close(),那么为了结果的准确性,我们在打印结果之前通过time.Sleep(time.Second * 1)先休眠等待1秒,使得连接完全被回收后,然后再打印输出结果。
因为,Close()过程可能需要一定时间,如果直接输出goroutine数量,那么可能出现连接还未完全回收的情况,导致结果随机,有时为1有时为3 …
而针对以上testFun4(),虽然执行了 resp.Body.Close(),但是同时也执行了 ioutil.ReadAll(resp.Body),这里会优先把当前的连接放入空闲列表中,供下次复用,所以不存在输出结果随机的情况。
解析:
首先要清楚,如果没有调用 resp.Body.Close(),也就是没有 回收/释放 连接,那一定会存在协程的泄漏问题(进而导致内存泄漏)。
另外,稍微了解 go net/http 包的同学,都知道 每次执行http的 Get/Post 请求时,底层都会创建两个协程,分别处理 写请求/读响应 这两个事件。具体底层逻辑后面会提到 …
所以你可以简单的理解为:执行一条http请求时,go内部会创建两个协程。
接下来,针对每一个示例做分析:
- testFun1() 这里执行了 5 次http请求,且不执行
resp.Body.Close(),也不执行ioutil.ReadAll(resp.Body):
因为 每次执行http的 Get/Post 请求时,底层都会创建两个协程,加上主协程本身,所以一共有 5*2 + 1 = 11 个协程。 - testFun2() 这里执行了 5 次http请求,执行
resp.Body.Close(),不执行ioutil.ReadAll(resp.Body):
说明在执行resp.Body.Close()后,回收了底层都会创建两个协程,只剩下主协程本身,所以一共就 1 个协程。 - testFun3() 这里执行了 5 次http请求,不执行
resp.Body.Close(),执行ioutil.ReadAll(resp.Body):
当执行ioutil.ReadAll(resp.Body),将 resp.body 读取完之后,会把当前的连接放入空闲列表中,供下次复用。 - testFun4() 这里执行了 5 次http请求,执行
resp.Body.Close(),也执行ioutil.ReadAll(resp.Body):
这里的结果和 testFun3() 一致,关键在于 执行了ioutil.ReadAll(resp.Body),将 resp.body 读取完之后,会优先把当前的连接放入空闲列表中,供下次复用,即使你执行了resp.Body.Close()。
源码解析:
通过跟踪 go net/http 包的源码,得到其调用链路的流程图:
可以发现每次新建立一个http请求,最终底层实际上都会创建两个新的协程(写请求/读响应)
go pconn.readLoop():启动一个 读响应 相关的goroutinego pconn.writeLoop():启动一个 写请求 相关的goroutine

readLoop()和 writeLoop()本身都是for循环:
- 只要【控制是否退出循环的变量
alive】为true,循环就会一直进行下去,且会把当前的连接放入空闲列表中,供下次复用。- 示例:Fn正常的读body,当body读完之后,会向
waitForBodyRead推入一个true:waitForBodyRead <- isEOF。
- 示例:Fn正常的读body,当body读完之后,会向
- 而只有从
bodyEOF := <-waitForBodyRead中读出的值为false,循环才会退出。
示例:earlyCloseFn 未读取body就close的会执行此方法,可以发现向waitForBodyRead推入一个false:waitForBodyRead <- false。- 若退出循环,最后会执行 readLoop 中的defer()函数。defer函数中的
pc.close(closeErr)不仅会关闭本身的通道closech,也会进而控制writeLoop的退出。

- 若退出循环,最后会执行 readLoop 中的defer()函数。defer函数中的
因此,waitForBodyRead这个chan对接下来goroutine的生死起着关键作用。

总结:
- 日常开发中,在写代码时基本都不会遗漏
ioutil.ReadAll(resp.Body),但可能会存在忘记写resp.Body.Close()的情况,这里可能会导致协程泄漏。
但如果你请求的域名 url 不变的话,那么顶多只会泄漏一个负责 读响应 的goroutine 和一个负责 写请求 的goroutine,不会导致协程数飙升的情况,所以程序运行一般也不会出现什么太明显的问题。 - 不过还是建议:在执行任何http 请求时,一定要记得加
resp.Body.Close(),避免异常情况下的goroutine泄漏,进而导致内存泄漏(每个goroutine初始时只占几kb,但量多了也扛不住),引起服务异常。
相关文章:
Golang http请求忘记调用resp.Body.Close()而导致的协程泄漏问题(含面试常见协程泄漏相关测试题)
参考: 知乎:别因为忘记close你的httpclient,造成goroutine泄漏 CSDN:resp.Body.Close() 引发的内存泄漏goroutine个数 先来看几道题,想一想最终的输出结果是多少呢? package mainimport ("fmt"…...
进程信号生命周期详解
信号和信号量半毛钱关系都没有! 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2 查看信号的机制,如默认处理动作man 7 signal SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理…...
2023-03-03干活小计
今天见识了 归一化的重要性:归一化 不容易爆炸 深度了解了学习率:其实很多操作 最后的结果都是改变了lr 以房价预测为例:一个点一个点更新 比较 矩阵的更新: 为什么小批量梯度下降 优于随机梯度下降 优于批量梯度下降ÿ…...
操作系统结构
随着操作系统的不断增多和代码规模的不断扩大,提供合理的结构对提升操作系统的安全与可靠性来说变得尤为重要。 1.分层法 指将操作系统分为若干层,最低层位硬件,最高层为用户接口,每层只能调用紧邻它的低层的功能和服务(类似于计…...
[SSD科普] 固态硬盘物理接口SATA、M.2、PCIe常见疑问,如何选择?
前言犹记得当年Windows 7系统体验指数中,那5.9分磁盘分数,在其余四项的7.9分面前,似乎已经告诉我们机械硬盘注定被时代淘汰。势如破竹的SSD固态硬盘,彻底打破了温彻斯特结构的机械硬盘多年来在电脑硬件领域的统治。SSD数倍于HDD机…...
【Java学习笔记】3.Java 基础语法
Java 基础语法 一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象ÿ…...
Python基础学习6——if语句
基本概念 if语句为条件判断语句,用来判断if后面的语句是真是假。if的用途有很多,比如作为条件测试可以判断两数是否相等与不等、进行数值笔记等等。例子如下: Lego_price (599, 799, 898) if Lego_price[0] 599:print("Correct!&quo…...
有免费的PDF转Word吗?值得收藏的7个免费 PDF转Word工具请收好
PDF 和 DOC 是人们在工作中广泛使用的两种最流行的文档格式。PDF 是 Adobe 的便携式文档格式,DOC 是 Microsoft 的 Word 文档格式。PDF 是一种更安全可靠的文件格式,因为它很难编辑 PDF 文件,但是有一些称为 PDF 编辑器的工具可用于编辑 PDF …...
Thinkphp6使用RabbitMQ消息队列
Thinkphp6连接使用RabbitMQ(不止tp6,其他框架对应改下也一样),如何使用Docker部署RabbitMQ,在上一篇已经讲了->传送门<-。 部署环境 开始前先进入RabbitMQ的web管理界面,选择Queues菜单,点…...
小成本互联网创业怎么做?低成本创业的方法分享
多数人都会有想法创业,尤其是在互联网上面创业,很多人看到了商机,但是因为成本的原因又放弃了,实际上,小成本也可以互联网创业!那么,小成本互联网创业怎么做?低成本创业的方法在这里…...
六、栈、栈的相关问题
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、栈 1.栈概述 2.栈的实现 2.1 栈的API 2.2 栈的实现 二、栈的括号匹配问题 1.问题描述 2.代码实现 三、逆波兰表达式求值问题 1.问题描述 2.代码 总结 前言 提…...
Java安全停止线程
Thread 类虽提供了一个 stop() 方法(已经被废弃),但由于 stop() 方法强制终止一个正在执行的线程,可能会造成数据不一致的问题,所以在生产环境中最好不要使用。 场景: 由于一些操作需要轮询处理ÿ…...
12 readdir 函数
前言 在之前 ls 命令 中我们可以看到, ls 命令的执行也是依赖于 opendir, readdir, stat, lstat 等相关操作系统提供的相关系统调用来处理业务 因此 我们这里来进一步看一下 更细节的这些 系统调用 我们这里关注的是 readdir 这个函数, 入口系统调用是 getdents 如下调试…...
Windows环境搭建Android开发环境-Android Studio/Git/JDK
Windows环境搭建Android开发环境-Android Studio/Git/JDK 因为休假回来后公司的开发环境由Ubuntu变为了Windows,所以需要重新配置一下开发环境。 工作多年第一次使用Windows环境进行开发工作,作次记录下来。 一、 Git安装 1.1git 标题软件下载 网址&…...
全国爱耳日丨听力受损严重有哪些解决办法
——【科学爱耳护耳,实现主动健康】随着数码电子设备使用越来越方便、日常使用时间越来越长,听力障碍、患上耳道疾病一系列问题也接踵而至,在当下我们必须重视听力健康,采取更科学的听音方式,保护听力健康,…...
【抽水蓄能电站】基于粒子群优化算法的抽水蓄能电站的最佳调度方案研究(Matlab代码实现)
👨🎓个人主页:研学社的博客💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密…...
【异常】因多租户字段缺少导致Error updating database. Column ‘tenant_id‘ cannot be null
一、报错内容 org.springframework.dao.DataIntegrityViolationException: ### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Column tenant_id cannot be null ### The error may exist in com/xxx/cloud/mall/admin/mapper/Goods…...
类和对象(上)
文章目录 面向对象的初步认知类的实例化this引用对象的构造及初始化封装static成员代码块内部类 对象的打印一、面向对象的初步认知 Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。在java中…...
Java经典面试题——谈谈 Java 反射机制,动态代理是基于什么原理?
典型回答 反射机制是 Java 语言提供的一种基本功能,赋予程序在运行时 自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法ÿ…...
19 客户端服务订阅机制的核心流程
Nacos客户端服务订阅机制的核心流程 说起Nacos的服务订阅机制,大家会觉得比较难理解,那我们就来详细分析一下,那我们先从Nacos订阅的概述说起 Nacos订阅概述 Nacos的订阅机制,如果用一句话来描述就是:Nacos客户端通…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
