Go每日一题(5) 的题目如下

4636 次点击 · 4 赞 ·大约8小时之前 开始浏览   · 来源「Go Questions」

Go 的 map 可以边遍历边删除吗?

4636 阅读
45 回复
jan-bar
jan-bar · #1 · 3年之前

清空map, :smile:

for k := range m {
    delete(m, k)
}
henry1
henry1 · #2 · 3年之前

打卡

xiaosi
xiaosi · #3 · 3年之前
jan-barjan-bar #1 回复

清空map, :smile: ```go for k := range m { delete(m, k) } ```

哈哈哈哈,骚操作

yayaleslie
yayaleslie · #4 · 3年之前

mark

minQie
minQie · #5 · 3年之前

di

Esac_Ben
Esac_Ben · #6 · 3年之前

Mark.

hasbug
hasbug · #7 · 3年之前

mark

a406299736
a406299736 · #8 · 3年之前

mark.....

mingtop
mingtop · #9 · 3年之前

没明白要考察什么

summers
summers · #10 · 3年之前

m

feiyang
feiyang · #11 · 3年之前

map 并不是一个线程安全的数据结构。同时读写一个 map 是未定义的行为,如果被检测到,会直接 panic。

brothersam
brothersam · #12 · 3年之前
        // Go1.11版本以上这种清空map方法有效
        for k := range mapdemo {
            delete(mapdemo , k)
        }
hasbug
hasbug · #13 · 3年之前

mark

Dessert
Dessert · #14 · 3年之前
jan-barjan-bar #1 回复

清空map, :smile: ```go for k := range m { delete(m, k) } ```

感谢。 note: 多个协程同时读写同一个 map 的情况下会因非线程安全panic。 如果在同一个协程内边遍历边删除,并不会检测到同时读写,理论上是可以这样做的。但是,遍历的结果就可能不会是相同的了,有可能结果遍历结果集中包含了删除的 key,也有可能不包含,这取决于删除 key 的时间:是在遍历到 key 所在的 bucket 时刻前或者后。

jan-bar
jan-bar · #15 · 3年之前

@Dessert</a> 我记得有一期的每日一题研究过range遍历,map的遍历是实时的,在遍历第1个元素时删除第2个元素,那么后续就不会遍历第2个元素。遍历第1个元素时删除第1个元素,后续更不会再出现第1个元素了。

https://go.dev/doc/effective_go#for , 这个官方例子也展示了可以在遍历的时候删除。

image.png

https://go.dev/ref/spec#For_statements , 同时官方的range迭代也有说着遍历时删除和新增的情况

image.png

我感觉清空map还是直接用m=make(map[string]string)生成新对象,让GC清理旧map好点,因为map的delete并不会真的删除里面元素,貌似只是标记被删除,这个比较底层没深入研究,这时还是会占用一些内存吧。

wzbwzt
wzbwzt · #16 · 3年之前

1

Alilestera
Alilestera · #17 · 3年之前

多个协程同时读写同一个 map,会得到如下的panic噢

fatal error: concurrent map iteration and map write

多协程下可以使用Go1.9版本引入的sync.Map类型来替换map

💡Tips:多协程读map是没问题的,但是写不行

761496606
761496606 · #18 · 3年之前
AlilesteraAlilestera #17 回复

多个协程同时读写同一个 map,会得到如下的panic噢 > fatal error: concurrent map iteration and map write 多协程下可以使用Go1.9版本引入的sync.Map类型来替换map 💡Tips:多协程读map是没问题的,但是写不行

怎么操作才panic的,为啥我的这样没问题

m := make(map[int]int, 0)
m[0] = 0
go func() {
    m[1] = 3
}()
go func() {
    a := m[1]
    fmt.Println(a)
}()

time.Sleep(3 * time.Second)
learningboy
learningboy · #19 · 3年之前
761496606761496606 #18 回复

#17楼 @Alilestera 怎么操作才panic的,为啥我的这样没问题 m := make(map[int]int, 0) m[0] = 0 go func() { m[1] = 3 }() go func() { a := m[1] fmt.Println(a) }() time.Sleep(3 * time.Second)

你数量设置大点啊,这么点数据,很快就执行完的

learningboy
learningboy · #20 · 3年之前
jan-barjan-bar #15 回复

@Dessert 我记得有一期的每日一题研究过`range`遍历,map的遍历是实时的,在遍历第1个元素时删除第2个元素,那么后续就不会遍历第2个元素。遍历第1个元素时删除第1个元素,后续更不会再出现第1个元素了。 https://go.dev/doc/effective_go#for , 这个官方例子也展示了可以在遍历的时候删除。 ![image.png](https://static.golangjob.cn/220923/2d31b234f61f9b55c1ec79f1e1d497e5.png) https://go.dev/ref/spec#For_statements , 同时官方的range迭代也有说着遍历时删除和新增的情况 ![image.png](https://static.golangjob.cn/220923/7a7bd664822e26524057441a9d68c8a0.png) 我感觉清空map还是直接用`m=make(map[string]string)`生成新对象,让GC清理旧map好点,因为map的delete并不会真的删除里面元素,貌似只是标记被删除,这个比较底层没深入研究,这时还是会占用一些内存吧。

map 是引用,所以虽然 walkrange 生成了一个 ha,但是修改 map,ha 也会收到影响。delete 只是把 tophash置为 emptyOne 或者 emptyRest,本质上 key 和 value 数组部分占用的内存还在,所以并不是真的删除,clear 会进行分配

Alilestera
Alilestera · #21 · 3年之前
761496606761496606 #18 回复

#17楼 @Alilestera 怎么操作才panic的,为啥我的这样没问题 m := make(map[int]int, 0) m[0] = 0 go func() { m[1] = 3 }() go func() { a := m[1] fmt.Println(a) }() time.Sleep(3 * time.Second)

你写的这个样例,我都不知道算不算多协程同时读写同一map了。每个协程的执行时间实在太短了,真要被检测出来的概率很低哈。

Dessert
Dessert · #22 · 3年之前
jan-barjan-bar #15 回复

@Dessert 我记得有一期的每日一题研究过`range`遍历,map的遍历是实时的,在遍历第1个元素时删除第2个元素,那么后续就不会遍历第2个元素。遍历第1个元素时删除第1个元素,后续更不会再出现第1个元素了。 https://go.dev/doc/effective_go#for , 这个官方例子也展示了可以在遍历的时候删除。 ![image.png](https://static.golangjob.cn/220923/2d31b234f61f9b55c1ec79f1e1d497e5.png) https://go.dev/ref/spec#For_statements , 同时官方的range迭代也有说着遍历时删除和新增的情况 ![image.png](https://static.golangjob.cn/220923/7a7bd664822e26524057441a9d68c8a0.png) 我感觉清空map还是直接用`m=make(map[string]string)`生成新对象,让GC清理旧map好点,因为map的delete并不会真的删除里面元素,貌似只是标记被删除,这个比较底层没深入研究,这时还是会占用一些内存吧。

学到了

Dessert
Dessert · #23 · 3年之前
jan-barjan-bar #15 回复

@Dessert 我记得有一期的每日一题研究过`range`遍历,map的遍历是实时的,在遍历第1个元素时删除第2个元素,那么后续就不会遍历第2个元素。遍历第1个元素时删除第1个元素,后续更不会再出现第1个元素了。 https://go.dev/doc/effective_go#for , 这个官方例子也展示了可以在遍历的时候删除。 ![image.png](https://static.golangjob.cn/220923/2d31b234f61f9b55c1ec79f1e1d497e5.png) https://go.dev/ref/spec#For_statements , 同时官方的range迭代也有说着遍历时删除和新增的情况 ![image.png](https://static.golangjob.cn/220923/7a7bd664822e26524057441a9d68c8a0.png) 我感觉清空map还是直接用`m=make(map[string]string)`生成新对象,让GC清理旧map好点,因为map的delete并不会真的删除里面元素,貌似只是标记被删除,这个比较底层没深入研究,这时还是会占用一些内存吧。

那就是做好直接通过赋值新map给原来的map ‘m’来清空是么, 剩下的交给GC

jan-bar
jan-bar · #24 · 3年之前

23楼 @Dessert 是的,因为map的底层是hash之,存数据越多的map后续存取应该会慢点,map重复使用个人感觉后续存值和取值的性能都会受到影响。除非像#20楼说的触发了map的clear机制,里面数据真的被清理掉。

huangyf168
huangyf168 · #25 · 2年之前

mark

wzbwzt
wzbwzt · #26 · 2年之前

1

hasbug
hasbug · #27 · 2年之前

mark

a406299736
a406299736 · #28 · 2年之前

111111

Dessert
Dessert · #29 · 2年之前

在多个协程同时读写同一个 map 的情况下,map 并不是一个线程安全的数据结构。同时读写一个 map 是未定义的行为,如果被检测到,会直接 panic。

在同一个协程内边遍历边删除,并不会检测到同时读写,理论上是可以这样做的。但是,遍历的结果就可能不会是相同的了,

Ysmword
Ysmword · #30 · 2年之前

同一个协程可以,多协程下不可以

beenleqi
beenleqi · #31 · 2年之前

mark

bsdx866
bsdx866 · #32 · 2年之前

image.png

flyZ
flyZ · #33 · 2年之前

mark

YuPeng
YuPeng · #34 · 2年之前

MARK

lijinmin
lijinmin · #35 · 2年之前

map 并不是一个线程安全的数据结构,同时读写一个map 是未定义的行为,如果被检测到,会直接panic

GO_go_GO1
GO_go_GO1 · #36 · 2年之前
761496606761496606 #18 回复

#17楼 @Alilestera 怎么操作才panic的,为啥我的这样没问题 m := make(map[int]int, 0) m[0] = 0 go func() { m[1] = 3 }() go func() { a := m[1] fmt.Println(a) }() time.Sleep(3 * time.Second)

map并发读写

euibieur894
euibieur894 · #37 · 2年之前

在多个协程同时读写同一个 map 的情况下,同时读写一个 map 是未定义的行为,如果被检测到,会直接 panic。如果在同一个协程内边遍历边删除,并不会检测到同时读写。

YuPeng
YuPeng · #38 · 2年之前

mark

528548004
528548004 · #39 · 2年之前

mark

hasbug
hasbug · #40 · 2年之前

mark

djh-A
djh-A · #41 · 大约1年之前

这问的时候也没说是多协程啊,只有一个协程内就可以删

YuPeng
YuPeng · #42 · 大约1年之前

mark

BigBigGopher
BigBigGopher · #43 · 大约1年之前

mark

hengbo
hengbo · #44 · 大约1年之前

一个协程内可以删, 多协程可能会死锁panic,map不是一个线程安全的结构,多协程可以使用sync.Map, 删除时,如果删除还没遍历到的key下的map,则后续不会再次遍历到这个key

BigBigGopher
BigBigGopher · #45 · 9月之前

mark

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传