当在循环内开启 goroutine 时,一定要非常小心地处理循环变量,避免它们在 goroutine 之间发生竞争,这是常见的 Go 并发问题之一。在当前给出的代码中,开启 goroutine 时使用的 tmpChain 变量就是循环变量 chain 的一个副本,但是它的值只会在循环开始时被赋值,而不会在每次迭代时重新赋值。因此,在你的代码里,如果开启了一些长时间运行的 goroutine 并且在迭代 chainCurrencyAddrMap 之后执行了它们,它们将会看到那个过时的 tmpChain 变量,而不是它们应该看到的当前值。这将导致 goroutine 处理的信息混乱,导致错误。
为了修复这个问题,一种解决方法是将循环变量传递给 goroutine 的参数中,这将确保在 goroutine 中使用的始终是变量的当前值。可以通过将 tmpChain := chain 改为 使用函数参数传递来实现。修改后的代码如下:
func getAddrDistInfoPatch(c * gin.Context,
rawData *graph_raw.GraphRawData,
chainCurrencyAddrMap map[string]map[string][]string,
column []string) (inFlowMap map[string]map[string]map[string]aModel.AddressInfo, err error) {
mu := &sync.Mutex{}
wg := &sync.WaitGroup{}
inFlowMap = make(map[string]map[string]map[string]aModel.AddressInfo)
for chain, currencyInfo := range chainCurrencyAddrMap {
for currency, addrs := range currencyInfo {
limit := make(chan bool, 50)
for i := 0; i < len(addrs); i += 50 {
start := i
wg.Add(1)
limit <- true
go func(c string, cr string) {
defer wg.Done()
group := collections.SliceSubRange(addrs, start, 50)
req := aModel.AddressInfoReq{
Direction: contract.LeftDirection,
Chain: c,
Currency: cr,
MinAmount: rawData.SessionInfo.MinAmount,
Column: column,
Addr: group,
BeginTime: rawData.SessionInfo.BeginTime,
EndTime: rawData.SessionInfo.EndTime,
}
tmp := map[string]aModel.AddressInfo{}
resp := safeis.Call(c, "address", "getDistCurrencyAddrInfo",
http.MethodPost, nil, req, nil)
if resp.Code != serror.Success {
slog.Errorf(c, "call address/getDistCurrencyAddrInfo failed, resp:%+v", resp)
return
}
_ = resp.ToStruct(&tmp)
<-limit
mu.Lock()
defer mu.Unlock()
for k, v := range tmp {
if _, ok := inFlowMap[k]; !ok {
inFlowMap[k] = make(map[string]map[string]aModel.AddressInfo)
}
if _, ok := inFlowMap[k][chain]; !ok {
inFlowMap[k][chain] = make(map[string]aModel.AddressInfo)
}
inFlowMap[k][chain][currency] = v
}
}(chain, currency)
}
}
}
wg.Wait()
return inFlowMap, nil
}
在上述代码中,通过将 tmpChain 赋值替换为将 chain 和 currency 作为参数传递给匿名函数,解决了迭代过程中 goroutine 错误描述的问题。