关于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())。

相关推荐
24zhgjx-fuhao3 分钟前
ISIS:单区域集成ISIS
网络·智能路由器
在繁华处4 分钟前
Java从零到熟练(十一):Spring框架入门
java·开发语言·spring
十五年专注C++开发6 分钟前
cereal 库:C++ 序列化的轻量之选
开发语言·c++·序列化·反序列化·cereal
喵了几个咪22 分钟前
AI重构软件开发范式:框架与脚手架为何仍是生产级开发的刚需?
vue.js·人工智能·react.js·重构·golang·ai编程
不爱洗脚的小滕23 分钟前
【RAG】Milvus 混合检索参数调优:ef / candidate_k / final_k 详解
网络·langchain·milvus·rag
星卯教育tony27 分钟前
2026年全国青少年信息素养大赛主题应用 数字守艺人 丝路新城 星火征程 智传民韵 c++ python scratch 所有真题免费分享
开发语言·c++
z落落38 分钟前
C# 继承:父子构造函数 + base 关键字 +五大访问修饰符(同项目+跨项目 全覆盖)
开发语言·c#
夜月yeyue1 小时前
KCP 与 UDP 可靠传输
linux·网络·单片机·网络协议·udp·php
一个向上的运维者1 小时前
Docker 自定义网络中容器无法通过宿主机 IP 访问服务的完整排障记录
网络·tcp/ip·docker
day day day ...1 小时前
MyBatis / MyBatis-Plus 动态 SQL 中 OGNL 表达式的常见陷阱与源码分析
java·开发语言·mybatis