go map并发读写问题

  1. 1 问题描述
  2. 2 代码分析
  3. 3 map结构

1 问题描述

用trpc重构看点精排后,灰度放量1%,测试用例一直告警,排查染色日志发现ip connection refused,登录粗排容器,发现进程刚重启,在日志目录发现有panic:map并发读写;

2 代码分析

接口请求结构体req *RoughRankAdReq中有成员AdsUserInfo,AdsUserInfo中扩展字段MExtendUserInfo map[string]string;

在召回游戏、合约、自营广告时并行起了多个goroutine,每个goroutine都调用自己RecallData的召回函数,但这些RecallData都有RoughRankAdReq成员,全部由请求结构体req *RoughRankAdReq赋值浅Copy;

在每个goroutine中:

  • 创建一个RecallAdReq结构体变量RecallReq;
  • 构造新的RoughRankAdReq结构体变量newReq,AdsUserInfo成员由RecallData的AdsUserInfo赋值浅Copy;
  • RecallAdReq的成员AdsUserInfo由newReq的AdsUserInfo赋值浅Copy;

问题就出在对于IEG召回源:

RecallReq.StAdsUserInfo.MExtendUserInfo["sysid"] = "kd"

导致在map MExtendUserInfo可能会并发读写,panic。

3 map结构

map的底层结构为:

// A header for a Go map.
type hmap struct {
    // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
    // Make sure this stays in sync with the compiler's definition.
    count     int // # live cells == size of map.  Must be first (used by len() builtin)
    flags     uint8
    B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
    noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    hash0     uint32 // hash seed

    buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

    extra *mapextra // optional fields
}

go结构体之间相互赋值,本质上是浅Copy,Copy了结构体内部的所有字段。因此上面描述的代码各结构体的AdsUserInfo的MExtendUserInfo map成员,底层指向的是同一块内存,故panic。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至yj.mapple@gmail.com

文章标题:go map并发读写问题

文章字数:418

本文作者:melonshell

发布时间:2020-11-01, 10:24:41

最后更新:2020-11-01, 11:12:00

原始链接:http://melonshell.github.io/2020/11/01/problem0/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏

相册