使用了 sync 的 map 在做自增操作的时候还有必要上锁吗

2022-04-10 21:11:57 +08:00
 wangnimabenma

    a, _ := monitor.ProgressBars[configName].Progress.Load("number")
    monitor.ProgressBars[configName].Progress.Store("number", a.(int)+1)
    

我的理解里,map 只能保证并发读写,但是保证不了 先读后写的自增,需要上锁

 m.Lock()
   a, _ := monitor.ProgressBars[configName].Progress.Load("number")
    monitor.ProgressBars[configName].Progress.Store("number", a.(int)+1)
 m.Unlock()

不知道哪种是正解,求前辈指点

933 次点击
所在节点    问与答
7 条回复
acehowxx
2022-04-10 21:36:04 +08:00
我觉得不用加锁。已经都用 sync.map 了还加啥锁。
wangnimabenma
2022-04-10 21:37:57 +08:00
@acehowxx #1 刚开始我也感觉不用加锁,但是有个情况没搞清楚。 Load 和 Store 是原子的, +1 操作是非原子的
acehowxx
2022-04-10 21:55:08 +08:00
@wangnimabenma 那个 store 是个函数,你+1 后就已经是那个函数的第二个参数了。而且你取出来的 a 也是 map 里值的副本,你改变 a 的值并不会改 map 里的值。
cloverstd
2022-04-10 22:37:36 +08:00
不加锁是 goroutine 安全的,但不是业务并发安全
number 应该存 *int64 ,然后用 atomic 去自增
wangnimabenma
2022-04-10 22:51:09 +08:00
wangnimabenma
2022-04-10 22:53:16 +08:00
@acehowxx #3 刚测试了下,sync map 的自增是不能并发安全的
```go
m := sync.Map{}
m.Store("test", 0)

go func() {
for true {
c,_ := m.Load("test")
m.Store("test", c.(int)+1)
time.Sleep(1 * time.Second)
fmt.Println(c)
}
}()

go func() {
for true {
c,_ := m.Load("test")
m.Store("test", c.(int)+1)
time.Sleep(1 * time.Second)
fmt.Println(c)
}
}()
go func() {
for true {
c,_ := m.Load("test")
m.Store("test", c.(int)+1)
time.Sleep(1 * time.Second)
fmt.Println(c)
}
}()
go func() {
for true {
c,_ := m.Load("test")
m.Store("test", c.(int)+1)
time.Sleep(1 * time.Second)
fmt.Println(c)
}
}()

time.Sleep(30 * time.Second)
```
wangnimabenma
2022-04-10 23:01:42 +08:00
@cloverstd #4 感谢,测试了下是这样的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/846128

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX