Go语言通过goroutine启动Watcher.Start出现配置实例切换问题
问题原因
你遇到的问题是Go语言for range循环的迭代变量复用特性导致的,和goroutine的调度时机共同作用产生了该现象:
- Go中
for _, watcherConfig := range configuration.Watchers写法里,watcherConfig是整个循环共用的同一块内存地址,每次迭代只会把对应配置项的值覆盖写入到这块内存中,不会重新分配新内存。 - 你在循环中调用
watcher.New(&watcherConfig)时,每次都把这个共用内存的指针传给了New方法,所有Watcher实例存储的config指针本质都指向同一个内存地址。 - 当你在
New中用goroutine异步执行Start()时,goroutine不会立即被调度执行,等循环执行完成后,watcherConfig所在内存已经被覆盖为最后一个配置项的值(也就是/var/log/postfix.log),此时两个goroutine才被调度执行,读取到的自然都是同一个地址的最终值。 - 同步调用
Start()时,每次迭代都会立即执行打印逻辑,此时watcherConfig的内容还没被下一次迭代覆盖,所以可以输出正确结果。
修复方案
你可以任选以下任意一种方案修复:
- 循环内创建局部变量拷贝:在循环内部新增一行局部变量声明,让每次迭代的配置使用独立内存地址:
for _, watcherConfig := range configuration.Watchers { watcherConfig := watcherConfig // 新增此行,创建当前迭代值的局部拷贝 watchers = append(watchers, watcher.New(&watcherConfig)) }
- 修改
New方法参数为值类型:如果WatcherConfig结构体体积不大,可以直接传值而非指针,自动完成拷贝避免共享内存:
// 修改New方法签名,用值类型接收参数 func New(config WatcherConfig) *Watcher { instance := &Watcher{ config: &config, // 取局部参数的地址,每个实例独立 connections: make(map[string]ConnectionInfo), lock: sync.Mutex{}, } go instance.Start() return instance }
New方法内部拷贝配置:如果不方便修改调用逻辑和方法签名,可以在New内部先拷贝配置再存储指针:
func New(config *WatcherConfig) *Watcher { // 拷贝传入的配置,使用独立内存 localConfig := *config instance := &Watcher{ config: &localConfig, connections: make(map[string]ConnectionInfo), lock: sync.Mutex{}, } go instance.Start() return instance }
内容的提问来源于stack exchange,提问作者TonyP.
相关产品推荐
相关产品推荐

