You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让仅一个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()
}

关键逻辑说明

  1. 状态保护:用mu互斥锁保护token和updating变量,避免并发读写导致的竞态问题。
  2. 更新抢占:当Goroutine检测到Token过期时,先检查updating状态:
    • 如果已有其他Goroutine在更新,则调用cond.Wait()进入等待,释放锁让其他Goroutine执行。
    • 如果没有更新中的Goroutine,则设置updating为true,抢占更新权。
  3. 更新通知:生成新Token后,更新token并将updating置为false,通过cond.Broadcast()通知所有等待的Goroutine:Token已更新,可以重试业务处理。
  4. 安全重试:等待的Goroutine被唤醒后,退出循环并重新调用Process,此时已经可以使用新生成的Token。

为什么不用context.WithCancel?

context.WithCancel更适合一次性的取消场景,而Token过期是可能重复发生的事件,循环中复用已取消的上下文会导致逻辑混乱。而互斥锁+条件变量的组合,天然适配这种需要重复等待状态变更的场景,逻辑更清晰且易于维护。

内容的提问来源于stack exchange,提问作者Flying Kitty

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 15:37:07