golang panic: assignment to entry in nil map

 

目录

Golang中,内建map切忌开箱即用

嵌套场景

golang中的map不是并发安全的

如何解决

或许你可以尝试下sync.Map

引用


Golang中,内建map切忌开箱即用

Golang中,map是引用类型,如指针切片一样,通过下面的代码声明后指向的是nil。这点在golang官方文档中也说明了,所以千万别直接声明后就使用,开始可能经常会犯下面的错:

var m map[string]string
m["result"] = "result"

上面的第一行代码并没有对map进行一个初始化,而却对其进行写入操作,就是对空指针的引用,这将会造成一个painc。所以,得记得用make函数对其进行分配内存和初始化:

m := make(map[string]string)
m["result"] = "result"

附: 官方文档解释

This variable m is a map of string keys to int values:
var m map[string]int
Map types are reference types, like pointers or slices, and so the value of m above is nil; it doesn’t point to an initialized map. A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic; don’t do that. To initialize a map, use the built in make function:
m = make(map[string]int) 

同为引用类型的slice,在使用append 向nil slice追加新元素就可以,原因是append方法在底层为slice重新分配了相关数组让nil slice指向了具体的内存地址

nil map doesn’t point to an initialized map. Assigning value won’t reallocate point address.
The append function appends the elements x to the end of the slice s, If the backing array of s is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.

嵌套场景

注意这种map的嵌套的形式,make只初始化了map[string] 部分,我理解是为外层map的key分配了内存空间,但是此时内层的map并没有初始化,所以下面的赋值会出现错误:

个人认为原文有误:原文链接

test := make(map[string]map[int]int)
test["go"][0] = 0 // error
  • 正确的做法:

test := make(map[string]map[int]int)
test["go"] = make(map[int]int)
test["go"][0] = 0

一个常用的做法:

test := make(map[string]map[int]int)
if test["go"] = nil {
    test["go"] = make(map[int]int)
}
test["go"][0] = 0

 

下文摘录自:https://studygolang.com/articles/17099

golang中的map不是并发安全的

map纵然很好用,但也得谨慎。或许很多人还不知道,golang内建map其实并不是并发安全的

下面自定义一个结构体,赋其map属性,给其绑定Get,Set方法方便操作。

// M
type M struct {
    Map    map[string]string
}

// Set ...
func (m *M) Set(key, value string) {
    m.Map[key] = value
}

// Get ...
func (m *M) Get(key string) string {
    return m.Map[key]
}

当你在一个test里面,在单个goroutine中跑的时候或许没什么问题,甚至10个goroutine,20个都还正常。

// TestMap  ...
func TestMap(t *testing.T) {
    c := helper.M{Map: make(map[string]string)}
    wg := sync.WaitGroup{}
    for i := 0; i < 21; i++ {
        wg.Add(1)
        go func(n int) {
            k, v := strconv.Itoa(n), strconv.Itoa(n)
            c.Set(k, v)
            t.Logf("k=:%v,v:=%v\n", k, c.Get(k))
            wg.Done()
        }(i)
    }
    wg.Wait()
    t.Log("ok finished.")
}

然而当你你再添加的时候,goroutine再增加的时候,会报下面的错,也就是map并发写入出错.

fatal error: concurrent map writes

goroutine 75 [running]:
runtime.throw(0x13b2011, 0x15)

需要说明一点的是,在Http请求时,我们通常对参数封装,encode,可能会调用golang自带的url.Values{},通过读源码可以发现也是线程不安全的。

如何解决

其实要解决上面的问题也不难,出错原因golang已经写得很清楚了,concurrent map writes,并发写map异常,这个时候肯定想的是并发操作上能不能解决。

很显然,我们可以用锁机制解决上面的问题。我们将上面的map结构改成如下:

// M
type M struct {
    Map    map[string]string
    lock sync.RWMutex // 加锁
}

// Set ...
func (m *M) Set(key, value string) {
    m.lock.Lock()
    defer m.lock.Unlock()
    m.Map[key] = value
}

// Get ...
func (m *M) Get(key string) string {
    return m.Map[key]
}

在上面的代码中,我们引入了锁机制操作,从而保证了map在多个goroutine中的安全。这时再执行我们的test会发现其正常输出。

=== RUN   TestMap
--- PASS: TestMap (0.00s)
    map_test.go:27: k=:5,v:=5
    map_test.go:27: k=:17,v:=17
    map_test.go:27: k=:20,v:=20
    map_test.go:27: k=:19,v:=19
    map_test.go:27: k=:6,v:=6

或许你可以尝试下sync.Map

golang中的sync.Map是并发安全的,其实也就是sync包中golang自定义的一个名叫Map的结构体。结构体原型如下:

type Map struct {
   mu Mutex
   read atomic.Value // readOnly
   dirty map[interface{}]*entry
   misses int
}

可以看见有 Mutex,很显然也是用了锁机制的,从而来保证了并发安全。该包中的Map提供了Store、Load、Delete、Range等操。并且sync包中的Map是开箱可用的,也即是声明后就可以直接使用,如下:

var m sync.Map
m.Store("method", "eth_getBlockByHash")
value, ok := m.Load("method")
t.Logf("value=%v,ok=%v\n",value,ok)

当然也可以用Range遍历

// TestMap  ...
func TestMap(t *testing.T) {
    var m sync.Map
    m.Store("method", "eth_getBlockByHash")
    m.Store("jsonrpc", "2.0")
   
    m.Range(func(key, value interface{}) bool {
        t.Logf("range k:%v,v=%v\n", key, value)
        return true
    })
}

结果如下:

=== RUN   TestMap
--- PASS: TestMap (0.00s)
    map_test.go:43: range k:jsonrpc,v=2.0
    map_test.go:43: range k:method,v=eth_getBlockByHash
PASS

 

 

引用

https://studygolang.com/articles/17099

https://blog.csdn.net/liuqun0319/article/details/111225035?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

https://blog.csdn.net/jason_cuijiahui/article/details/79410471?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-3 utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

https://blog.csdn.net/qq_36025814/article/details/113565958?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control

 

 

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值