函数并发安全取决于其内部实现而非调用goroutine数量;纯函数天然安全,但涉及全局变量、共享内存或非安全外部调用则不安全,需同步机制保障。

函数本身是否并发安全,只取决于它内部做了什么,而不是你调用它的 goroutine 有多少个。 如果函数只操作局部变量、不碰全局状态、不改共享内存、不依赖未同步的外部资源,那哪怕一百万个 goroutine 同时调用它,也完全没问题。
纯函数(无状态、无副作用)天然并发安全
像 func add(a, b int) int { return a + b } 这类函数,每次调用都在自己栈帧里算,参数和返回值都不逃逸,不存在共享数据。Go 运行时自动隔离每个 goroutine 的栈空间,所以这种函数根本不需要加锁、也不用 sync.Once 或其他同步机制。
- ✅ 安全前提:不读写包级变量(如
var counter int) - ✅ 不修改传入的指针或切片底层数组(除非明确加锁)
- ✅ 不调用非并发安全的外部函数(比如未加锁的
map写入、rand.Seed()) - ❌ 闭包捕获的循环变量(如
for i := range xs { go func(){ use(i) }() })会导致所有 goroutine 共享同一个i值,这是常见坑
带共享状态的幂等函数 ≠ 并发安全
幂等性只保证“多次调用效果等于一次”,但不解决执行过程中的竞态。比如一个初始化函数用 sync.Once 包裹了 DB 连接逻辑,但如果内部还往全局 map 里写数据,照样会触发 fatal error: concurrent map writes。
- ⚠️ 常见错误现象:
concurrent map writes、返回值不一致(两个 goroutine 都成功写入同一 key)、计数器错乱 - ? 正确做法:把所有可变状态收进结构体字段,用
sync.Once控制初始化入口,避免包级变量 - ? 需要按参数(如
Load(key string))独立幂等时,sync.Once失效,得用sync.Map+ CAS 或自定义状态标记
fmt.Print 等标准输出不是 goroutine 安全的
多个 goroutine 直接调用 fmt.Println 或 fmt.Printf,输出内容大概率会交错混杂,这不是偶发 bug,而是底层 os.Stdout.Write() 系统调用非原子导致的确定性问题——尤其在 GOMAXPROCS > 1、输出较长、或写入 os.Stderr(默认无缓冲)时极易复现。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- ❌ 别指望
GOMAXPROCS=1能救场:goroutine 仍可能被抢占(比如遇到time.Sleep、channel 操作) - ✅ 可靠方案:用
sync.Mutex包裹fmt调用,或改用log包(它内部已加锁),或通过 channel 统一收集再打印 - ? 注意:即使单次
fmt.Print("ABC")看似简单,底层也可能拆成多次write()系统调用,中间就被插队
指针方法的并发安全性取决于方法体实现
多个 goroutine 调用同一个结构体指针的某个方法,是否安全,和接收器是值还是指针无关,只看这个方法干了什么。例如 func (s *Service) Get() string,如果它只读字段、不改状态,就安全;如果它更新 s.cache 且没加锁,就会出问题。
- ✅ 安全场景:只读字段、计算局部值、调用其他并发安全函数
- ❌ 危险场景:修改接收器指向的字段、写全局变量、操作未加锁的共享
map或切片 - ? 方法不具备重入性(non-reentrant)时尤其危险:比如方法依赖某个临时状态在执行中不变,但并发调用会破坏该假设
真正容易被忽略的是:函数签名看不出并发风险,必须看内部实现。一个看似无害的 func LoadConfig(),如果它偷偷往包级 configMap 里塞数据,那就不是并发安全的——得翻源码,不能只看调用方式。

















