-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
每日一问-map #332
Comments
个人思考: go的map实现其实相对算比较简单,推荐先看下map作者的介绍视频https://www.youtube.com/watch?v=Tl7mi9QmLns&t=3s 视频里面也提到了取地址的原因,以及map的动态扩容过程,和上面的推测基本吻合。但视频并没有直接说m["woofy"].age++这种操作非法的原因,依旧需要我们自己进一步思考。 首先,业务代码不能取地址并不是说map操作过程中都没有发生取址操作,实际上无论什么value类型,runtime都是通过返回value的地址来进行的,比如查找: //v = m[k] 被编译为如下runtime过程:
pk := unsafe.Pointer(&k)
pv := runtime.lookup(typeOf(m), m, pk)
v = *(*V)pv
package runtime
func lookup(t *mapType, m *mapHeader, k unsafe.Pointer) unsafe.Pointer 注意到 那么为什么 我们在看这类设计问题的时候,不能只看一个例子,像 m["woofy"].age = foo(m["woofy"], ...) + bar(&m["woofy"].age, ...) + 1 其中 |
正常情况下不应该是 |
是的,我在问题里也提到了。 |
考虑一个value为struct的map比如
考虑以下一些操作
为啥取地址和修改内容这类看起来还算"正常"的操作会编译报错?
一个常见的解释是,go的map数据是动态扩容和可迁移的,内存地址会过期,所以不能取地址,即value不是addressable的,因而不能单独更新结构体字段,一般用结构体指针作为value比如改用
map[string]*User
, 上述那样赋值就没问题了.那么问题来了
假设
append
发生了underlying array的重新分配,但此后s
和s[i]++
之类的操作依然是有效的, 为什么不用特别注意呢.m["woofy"].age++
这样的操作非法. 因为从语义上来说, 执行到这句的时候m["woofy"]
一定是存在、完整且无歧义的,map应当是采用逐个value均摊搬迁的策略来进行扩容,当前不会有其他(非业务)线程在操作map,也不会出现只搬了结构体一半字段的情况. 那么为什么当下不能对这个m["woofy"]
实体做修改呢?为什么说value这个过程中一定是存在完整且无歧义的呢,举例来说,假设map按如下常见方式来处理扩容:
a) 如果m1中key/value不存在,则写到m2,同时如果m1不空,从m1中任取一个key/value迁移到m2
b) 如果m1中存在,从m1中删除,再按a)处理
当m1为空时,完成动态扩容. � m1地址回收,因而之前在m1中的value会全部失效,故编译器禁止取地址.
但是同时可以看到,不论什么状态下,map中的每个存在的value都是完整且无歧义,因而理论上编译器可以允许类似m["woofy"].age++的操作,你说对不对呢?
The text was updated successfully, but these errors were encountered: