关于Go的init函数执行顺序#黑魔法

又又又学到新东西

1. 先来看一段代码

go 复制代码
var _ = func() struct{} {
	http.DefaultTransport = &http.Transport{
		Proxy:               http.ProxyFromEnvironment, //支持系统代理
		MaxIdleConns:        400,                       // 最大空闲连接数
		MaxIdleConnsPerHost: 80,                        // 保留80个空闲连接复用
		MaxConnsPerHost:     150,                       // 限制每个主机的最大连接数
		IdleConnTimeout:     90 * time.Second,
		TLSHandshakeTimeout: 10 * time.Second,
		ForceAttemptHTTP2:   true,
	}
	return struct{}{}
}()

乍一看,我去这是什么用法?

是不是很像 var num int 这一种全局变量的用法呢,没错其实这一种用法,和这个全局定义十分类似,这其实是一个匿名自执行函数 + 包级变量初始化的组合技。

拆开来看:

部分 作用
func() struct{} { ... } 定义一个匿名函数,返回一个空的 struct{}
(...)() 立即执行这个匿名函数
var _ = 把执行结果赋值给一个"下划线"变量(丢弃结果,只为触发副作用)
整体 在包初始化(package init)阶段就强制执行这个函数

结论:这段代码会在当前包被导入或程序启动时,自动执行一次,把 http.DefaultTransport 替换成我们自定义配置好的 Transport。

2. 为什么不直接写成 init() 函数?

有两种常见写法是等价的:

写法1(你看到的这种):

go 复制代码
var _ = func() struct{} {
    http.DefaultTransport = &http.Transport{...}
    return struct{}{}
}()

写法2(更直观的):

go 复制代码
func init() {
    http.DefaultTransport = &http.Transport{...}
}

两者执行时机完全一样,但是在真实场景下,还是建议使用第一种方式,原因有几个:

  1. 多个 init(),他们的执行顺序是不确定的

    Go 允许一个包有多个 init() 函数,执行顺序不保证。如果你想明确控制"这个初始化必须最先执行",用 var _ = func()...() 写在文件最上面就能强制排在所有 init() 前面(因为 var 初始化比 init() 更早)。

  2. 避免污染 init() 命名空间

    有些团队觉得 init() 太多会乱,喜欢用这种"无名"方式。

  3. 看起来更"一次性"

    一眼就看出这代码只运行一次,没副作用返回。

3. 这么改 DefaultTransport 有什么实际意义?

http.DefaultClient(也就是大家经常直接用的 http.Get、http.Post)底层就是用的 http.DefaultTransport

如果你不改,默认值大概是:

go 复制代码
MaxIdleConns:        100
MaxIdleConnsPerHost: 2   // ←←← 这才是最致命的!!
IdleConnTimeout:     90s

MaxIdleConnsPerHost 默认只有 2!

意味着你同一个主机(比如 api.ai.com),即使开了几百个 goroutine 并发请求,最多也只复用 2 个空闲连接,其他的都会重新建连接 → 性能极差,容易把对端打挂,或者被限流。

改成 80~100 之后,才真正发挥出 Go 高并发优势,典型生产环境配置:

go 复制代码
MaxIdleConns:        400-1000   // 总空闲连接池大小
MaxIdleConnsPerHost: 50-200    // 单个主机复用连接数(关键!)
MaxConnsPerHost:     0 或很大  // 0 表示不限制(Go 1.11+ 推荐)

一句话总结:这是 Go 高并发服务必备的"全局连接池优化"黑魔法,几乎所有中大型 Go 项目启动文件里都有这一段(或等价的 init())。

相关推荐
不才不才不不才10 分钟前
Spring 源码系列(14): JDK 动态代理 vs CGLIB,以及拦截器责任链
java·开发语言·spring
mifengxing11 分钟前
计算机组成原理——存储器系统
开发语言·考研·计算机组成原理·复习笔记·计算机408
aixingkong92131 分钟前
AI超节点Scale Up域总线各层优化设计
linux·服务器·网络
Yolanda_20221 小时前
Python学习-第九部分-错误处理与异常处理
开发语言·python·学习
郝学胜-神的一滴1 小时前
Qt 高级编程 037:QSS按钮样式封神指南
开发语言·c++·qt·软件工程·用户界面
DFT计算杂谈1 小时前
SCIENTIFIC REPORTS 非 van der Waals可调二维磁性材料设计平台
开发语言·php
库拉库拉里1 小时前
深入理解 C# 异步编程:同步、Task.Wait () 与 await 的本质区别及实践指南
开发语言·c#
霍霍的袁2 小时前
【C++】string类 从 入门使用 到 底层深浅拷贝模拟实现
开发语言·c++·visual studio
酷在前行2 小时前
【R绘图】Nature Communications 半眼图复刻:分布、区间与多面板排版(保姆级教程)
android·开发语言·r语言
C++、Java和Python的菜鸟2 小时前
第10章 后端Web进阶(Maven高级)
开发语言·python