Go语言中并发调用时如何实现共享数据仅单次获取?
解决方案:并发共享资源加载,避免重复请求
要实现多个goroutine共享同一次资源加载的结果,核心是通过互斥锁+条件变量同步加载状态,让后续请求等待首次加载完成后直接复用结果。以下是具体实现:
核心实现代码
import ( "math/rand" "sync" "time" "fmt" ) type ConfigLoader struct { mu sync.Mutex cond *sync.Cond config int64 loaded bool // 可选:添加过期时间支持配置自动刷新 // expireAt time.Time } func NewConfigLoader() *ConfigLoader { cl := &ConfigLoader{} cl.cond = sync.NewCond(&cl.mu) return cl } // GetConfig 获取配置,若未加载则等待加载完成 func (cl *ConfigLoader) GetConfig() int64 { cl.mu.Lock() defer cl.mu.Unlock() // 可选:检查配置是否过期,过期则标记为未加载 // if cl.loaded && time.Now().After(cl.expireAt) { // cl.loaded = false // } // 循环等待直到配置加载完成 for !cl.loaded { cl.cond.Wait() } return cl.config } // LoadConfig 加载配置,仅当配置未加载时执行 func (cl *ConfigLoader) LoadConfig() { cl.mu.Lock() if cl.loaded { cl.mu.Unlock() return } cl.mu.Unlock() // 模拟从S3获取配置的耗时操作 rand.Seed(time.Now().UnixNano()) config := rand.Int63() cl.mu.Lock() cl.config = config cl.loaded = true // 可选:设置过期时间 // cl.expireAt = time.Now().Add(5 * time.Minute) // 唤醒所有等待的goroutine cl.cond.Broadcast() cl.mu.Unlock() } // Refresh 主动刷新配置,触发重新加载 func (cl *ConfigLoader) Refresh() { cl.mu.Lock() cl.loaded = false cl.mu.Unlock() cl.LoadConfig() } func main() { loader := NewConfigLoader() var wg sync.WaitGroup wg.Add(2) // 并发启动两个请求 go func() { defer wg.Done() fmt.Println("#1 start") loader.LoadConfig() fmt.Printf("#1 result %d\n", loader.GetConfig()) }() go func() { defer wg.Done() fmt.Println("#2 start") loader.LoadConfig() fmt.Printf("#2 result %d\n", loader.GetConfig()) }() wg.Wait() // 后续请求,刷新配置后获取新结果 fmt.Println("#3 start") loader.Refresh() fmt.Printf("#3 result %d\n", loader.GetConfig()) }
关键逻辑说明
- 状态保护:用
sync.Mutex锁保护loaded状态和config结果,确保并发下状态修改的原子性。 - 等待机制:通过
sync.Cond条件变量,让未抢到加载权的goroutine进入等待状态,避免重复发起加载请求。 - 结果共享:首次加载完成后,调用
Broadcast()唤醒所有等待的goroutine,它们会直接获取已加载的配置结果。 - 可扩展性:若需要支持配置过期刷新,只需在
GetConfig中添加过期检查,标记loaded为false即可触发重新加载,并发请求仍会共享同一次刷新的结果。
输出效果
运行上述代码会得到符合预期的结果:
#2 start #1 start #1 result 5577006791947779410 #2 result 5577006791947779410 #3 start #3 result 6129484611666145821
内容的提问来源于stack exchange,提问作者Stéphane Bruckert
相关产品推荐
相关产品推荐

