据说map应该是人类发明的一种最有效的数据结构了,再回头整理这篇,在golang中,使用map的时候多多少少总会遇到一些小问题,比如可达性问题,地址分配问题,这里一起记录一下。
地址可达性
在go playground中有这样的例子:
package main |
这样在执行的时候会报一个常见的错误:cannot assign to items[“q”].A
改变一下value的声明方式,之后再进行类似的操作就可以修改了:
package main |
在golang设计的时候,map中的value值应该是地址不可达的,就是说直接取map中的元素的地址会报错,比如把上面例子中的main函数改成下边这样:
itemsb := make(map[string]Type) |
会报出cannot take the address of itemsb[“p”]的错误。原因大致是因为,在golang中,一个容量不断增长的map可能会导致原来map中的一些元素发生rehashing,使得他们被重新分配到新的storage location上,这样可能会导致原先得到的address变得不可用。就是所谓的map member 的 not addresable。
正如这个答案中写的,map的indexing操作本来就是地址不可达的,这和golang中map的具体实现机制有关,golang中的map并没有保证它们的value值的地址是不可变的,因为value值的地址很有可能被重新分配,就像前面所说的那样。一个修改的办法就是把value值设置成为指针的形式,这样就相当于添加了一个额外的entry,即使真正需要的那个值的位置发生了变化,也可以redirection过去。以后使用map声明一个结构体的value值的时候,这一点要特别注意下。
对于slice的index操作就是地址可达的,对于map则是不可达的,总是使用map的时候要特别注意下。
map is a reference to the map contents, but it does not hold references (unless it explicitly stores pointers)
动态修改map中的元素
就像上面部分所介绍的,如果map中的value是一个struct元素,那么是没有办法直接修改其值的,因为是地址不可达的。如果想要修改,就需要生成一个新的结构体实例,然后把旧的实例替换掉。
删除map中的元素
就是直接使用delete关键字进行操作就ok了
concurrent map writes
golang中的map并不是线程安全的(c/c++的stl也不是,大多是出于效率的考量),感觉自己在第一次写一个程序的时候,很少会考虑到线程安全这种事情,google一下,甚至是好多评价比较高的开源项目也会有类似的错误,因为这个错误带有一定的随机性。
其实具体解决的方案,也比较简单,就是找到具体的对数据进行读写操作的地方,然后加上个锁,比如参考beego里面一个类似问题的发现和解决。
首先需要定位一下,在哪种情况下,会发生这个错误,参考这里,我们根据这个例子来重现下cuncurrent map write的错误,下面这个程序和以认为是并发情况的一个测试,i从1到100遍历,每次可以认为i是同时并发(模拟的并发)的对map进行操作的goroutine,每次都创建出i个goroutine,对map进行操作。
package main |
由于goroutine的调度机制的问题,出现concurrent map read and map write的时机是不确定的,但是在没有加锁的时候,会以很大的概览出现这个问题。
之后把注释的内容恢复过来,在map读写的时候加上锁,然后再进行测试:
package main |
按照上述的方式来使用代码,多次测试也没有出现异常,都是被正常调度运行。
注意 留意下这里waitgroup的使用,对于goroutine的测试,使用waitgroup是方便多了,有点相当于是引用计数的功能,wg.Add(workers)就是把workers个计数加入到waitgroup中,之后wg.Done,就是执行wg.Add(-1),即是把引用计数减1,在最后的一步,执行wg.Wait()就是一直block在这里,直到wait group中的引用数目变成0。在测试goroutine的相关功能的时候,waitgroup的相关操作还是很好用的。
在stack over flow上面有一个很详细的介绍golang中锁机制使用的帖子。
通过阅读源代码我们可以知道sync.RWMutex是基于sync.Mutex实现的,其中的只读锁的实现使用类似引用计数的方式。
对于具体的全局锁,以及读锁,写锁的使用和区别,可以参考这个
相关参考
https://golang.org/ref/spec#Address_operators
https://code.google.com/p/go/issues/detail?id=3117
https://groups.google.com/forum/#!topic/golang-nuts/4_pabWnsMp0
http://stackoverflow.com/questions/13101298/calling-a-pointer-method-on-a-struct-in-a-map
https://golang.org/ref/spec#Address_operators
http://stackoverflow.com/questions/16059038/can-not-assign-to-a-struct-member-from-map
https://www.reddit.com/r/golang/comments/4a8y8c/go_16_fatal_error_concurrent_map_read_and_map/
权威参考 golang maps in action:
https://blog.golang.org/go-maps-in-action