如何让仅一个Goroutine更新Token,其余复用新Token?
解决方案:利用互斥锁+条件变量实现单Goroutine更新Token
核心思路是通过互斥锁保护更新状态,结合条件变量让等待的Goroutine感知Token更新完成,确保同一时刻只有一个Goroutine负责生成新Token,其余Goroutine等待更新完成后直接复用新Token。
修改后的完整代码
package main import ( "errors" "fmt" "math/rand" "sync" "time" ) type Manager struct { mu sync.Mutex // 保护token、updating状态 token string // 共享的Token updating bool // 标记是否有Goroutine正在更新Token cond *sync.Cond // 条件变量,用于通知等待的Goroutine更新完成 } func randString() string { // 模拟生成随机Token,替换为实际生成逻辑 return fmt.Sprintf("token-%d", rand.Intn(1000)) } func NewManager() *Manager { m := &Manager{} m.cond = sync.NewCond(&m.mu) // 初始化第一个Token m.mu.Lock() m.token = randString() m.mu.Unlock() fmt.Println("initial token generated:", m.token) return m } func (m *Manager) Work(countryCode string) { for { err := m.Process(countryCode) if err == nil { fmt.Printf("country %s: process success with token %s\n", countryCode, m.getToken()) break } // 检测到Token过期,尝试触发更新 m.mu.Lock() // 等待其他正在更新的Goroutine完成 for m.updating { fmt.Printf("country %s: waiting for token update...\n", countryCode) m.cond.Wait() } // 抢占更新权 m.updating = true m.mu.Unlock() // 安全生成新Token(此时无其他Goroutine在更新) newToken := randString() fmt.Printf("country %s: generating new token: %s\n", countryCode, newToken) // 更新Token并通知所有等待的Goroutine m.mu.Lock() m.token = newToken m.updating = false m.mu.Unlock() m.cond.Broadcast() } } // Process 模拟业务处理,使用Token并可能返回过期错误 func (m *Manager) Process(countryCode string) error { m.mu.Lock() currentToken := m.token m.mu.Unlock() // 模拟Token过期的随机错误(1/2概率) randNum := rand.Intn(2) if randNum == 1 { fmt.Printf("country %s: process failed with token %s (expired)\n", countryCode, currentToken) return errors.New("token expired") } return nil } // getToken 安全获取当前Token func (m *Manager) getToken() string { m.mu.Lock() defer m.mu.Unlock() return m.token } func main() { rand.Seed(time.Now().UnixNano()) countryList := []string{"AE", "DE", "AU"} m := NewManager() var wg sync.WaitGroup for _, countryCode := range countryList { wg.Add(1) go func(code string) { defer wg.Done() m.Work(code) }(countryCode) } wg.Wait() }
关键逻辑说明
- 状态保护:用
mu互斥锁保护token和updating变量,避免并发读写导致的竞态问题。 - 更新抢占:当Goroutine检测到Token过期时,先检查
updating状态:- 如果已有其他Goroutine在更新,则调用
cond.Wait()进入等待,释放锁让其他Goroutine执行。 - 如果没有更新中的Goroutine,则设置
updating为true,抢占更新权。
- 如果已有其他Goroutine在更新,则调用
- 更新通知:生成新Token后,更新
token并将updating置为false,通过cond.Broadcast()通知所有等待的Goroutine:Token已更新,可以重试业务处理。 - 安全重试:等待的Goroutine被唤醒后,退出循环并重新调用
Process,此时已经可以使用新生成的Token。
为什么不用context.WithCancel?
context.WithCancel更适合一次性的取消场景,而Token过期是可能重复发生的事件,循环中复用已取消的上下文会导致逻辑混乱。而互斥锁+条件变量的组合,天然适配这种需要重复等待状态变更的场景,逻辑更清晰且易于维护。
内容的提问来源于stack exchange,提问作者Flying Kitty
相关产品推荐
相关产品推荐

