Go中全局变量实为包级变量,须定义在函数外、import后,用var声明且首字母决定导出性;多文件初始化顺序不可控,需避免跨文件依赖;并发读写必须同步。

全局变量在 Go 中只能定义在包级别,不能在函数内
Go 不允许在函数内部用 var 声明包级变量,这是语法错误。所谓“全局变量”,实际是包级变量(package-level variable),必须位于函数外、文件顶层。
常见错误现象:syntax error: non-declaration statement outside function body,通常是因为把 var name = "x" 写在了函数外面但又没加 func 或其他合法声明上下文。
- 正确位置:文件顶部,import 块之后、任何
func之前 - 不能用
:=定义包级变量,必须用var显式声明 - 可省略类型,由初始化值推导,例如:
var DebugMode = true - 若需延迟初始化(如依赖运行时配置),应改用
sync.Once+ 函数封装,而非试图“懒加载”包级变量
首字母大小写决定包外是否可访问
Go 没有 public/private 关键字,可见性完全由标识符首字母决定:大写(Exported)表示导出,小写(unexported)即包内私有。
使用场景:你写一个工具包 utils,想让调用方能读写某个计数器,就命名为 Counter;如果只是内部状态缓存,就叫 cache —— 后者在其他包里根本看不到。
立即学习“go语言免费学习笔记(深入)”;
-
var MaxRetries = 3→ 其他包可通过utils.MaxRetries访问 -
var maxRetries = 3→ 仅utils包内可用,外部无法引用 - 结构体字段也遵循同一规则:
type Config struct { Port int; host string }中只有Port可被外部读写 - 注意:小写变量即使被反射(
reflect)读到,也无法直接赋值,会 panic
多个 var 块与初始化顺序影响依赖安全
同一个文件中多个 var 块之间按从上到下顺序初始化,跨文件则按编译器遍历顺序(一般为文件名字典序),不可控。这对有依赖关系的包级变量很危险。
常见错误现象:变量 A 初始化时依赖变量 B,但 B 所在文件被后编译,导致 A 得到零值(如 nil 指针或空字符串),运行时报 panic: runtime error: invalid memory address。
- 尽量避免包级变量间相互初始化,尤其跨文件时
- 若必须依赖,统一收口到一个
init()函数中,显式控制顺序 - 更稳妥的做法:把需要协作初始化的变量封装进结构体,用工厂函数返回(如
NewService()),延迟到首次调用时构造 - 常量(
const)无此问题,因为编译期确定,可安全用于其他变量初始化
并发读写包级变量必须加锁或用原子操作
Go 的包级变量本质是内存地址共享,多 goroutine 直接读写会导致数据竞争(data race)。Go 编译器不会阻止,但 go run -race 能捕获。
性能影响:频繁用 sync.Mutex 保护简单变量(如计数器)会成为瓶颈;而 sync/atomic 对基础类型更轻量,但不支持结构体或指针解引用。
- 只读场景(如配置项):定义为
var Config = struct{...}{...}即可,无需同步 - 简单整数增减:优先用
atomic.AddInt64(&counter, 1),比 Mutex 快一个数量级 - 复杂状态更新(如 map 修改):必须用
sync.RWMutex,且注意不要在锁内做耗时操作 - 切忌用局部变量临时“复制”包级变量再修改——这解决不了竞态,只是掩盖问题
包级变量的生命周期和程序一致,但它的可见性、初始化时机、并发安全性,三者任何一个没对齐,都会在上线后某次高并发或配置变更时突然暴露。尤其是跨文件初始化顺序和小写字母拼写,最容易被忽略。


















