包级全局变量必须在函数外声明,位于package声明后、任何func前;导出变量首字母大写,非导出则小写;推荐用var块集中声明;并发访问需同步防护。

包级别全局变量必须在函数外声明
Go语言不允许在函数内部用 var 声明包级变量,所有包级变量必须位于函数、方法、接口定义之外。如果写在 func main() 里,那就是局部变量;写在文件顶部(但仍在任何函数外),才属于包级别。
常见错误是把初始化逻辑塞进 init() 函数却忘了真正声明变量本身——init() 只能赋值,不能声明新标识符。
- 正确位置:源文件最外层,
package声明之后、任何func之前 - 可带初始值,也可不带(此时按类型零值初始化)
- 若未加
var关键字,仅用name := value,会报错non-declaration statement outside function body
导出与非导出全局变量的大小写规则
Go通过首字母大小写控制包外可见性:首字母大写才能被其他包引用。这点对全局变量同样适用,且极易忽略。
例如 Counter 可被 import 它的包读写,而 counter 只能在本包内使用。没有 public/private 关键字,全靠命名。
立即学习“go语言免费学习笔记(深入)”;
- 导出变量:首字母大写,如
MaxRetries、DefaultTimeout - 非导出变量:首字母小写,如
cache、mu(常配合sync.RWMutex使用) - 误将
dbConn写成DbConn可能意外暴露内部连接,引发并发误用
用 var 块集中声明多个全局变量
比起逐行写 var a = 1; var b = "x"; var c bool;,用 var 块更清晰,也方便统一注释和对齐。Go 会自动推导类型,但显式写出类型(尤其接口或指针)能提升可读性。
注意:块内若混用显式类型和类型推导,必须确保所有行都用 = 赋值(不能只写 var name type 单独一行),否则编译失败。
var (
Version = "v1.2.0"
DebugMode = false
DB *sql.DB
mu sync.RWMutex
)
- 同一
var块中,未指定类型的变量会按右侧表达式推导 - 显式声明类型(如
DB *sql.DB)时,不能跟=,否则语法错误 - 跨文件共享变量?不行——Go 没有“extern”概念,需通过导出变量 + 初始化函数(如
SetDB(*sql.DB))间接传递
并发读写全局变量必须加锁或用原子操作
全局变量天然跨 goroutine 共享,但 Go 不做默认同步。直接读写 int 或 map 类型变量在并发下必然出错,运行时可能 panic(如 fatal error: concurrent map writes),也可能静默产生脏数据。
别指望“我只是读,不会写”就安全——除非变量初始化后彻底不变(即 const 或 sync.Once 初始化一次后只读),否则必须防护。
- 读多写少:用
sync.RWMutex,读用RLock()/RUnlock(),写用Lock()/Unlock() - 简单整数计数器:优先用
atomic.Int64或atomic.AddInt64(),比锁更轻量 - 切片或 map:不要直接暴露全局
map[string]int,封装成带锁的方法,如Inc(key string)、Get(key string) int
最容易被忽略的是:把全局变量当配置中心用,却忘了它不是线程安全的“单例”。哪怕只是启动时初始化、运行中只读,也要确认所有 goroutine 都在初始化完成后再开始执行——这通常需要 sync.Once 配合。


















