Go 读取全局变量要加锁?!

2019-03-13 16:48:33 +08:00
 index90

在看同事的代码时候,发现这样的操作:

var (
  object = New()
  mu = sync.RWMutex
)

func SetValue(v SomeInterface) {
  mu.Lock()
  object = v
  mu.Unlock()
}

func GetValue() SomeInterface {
  mu.RLock()
  defer mu.RUnlock()
  return object
}  

为什么要加读锁啊?!难道会 Get 到 nil ? 同事给了我这个链接: https://stackoverflow.com/questions/21447463/is-assigning-a-pointer-atomic-in-golang

10770 次点击
所在节点    Go 编程语言
51 条回复
seaguest
2019-03-13 16:59:28 +08:00
加锁是为了防止脏读啊。
你读了旧的数据,然而刚刚被更新,读写锁就是为了解决这个的。
xkeyideal
2019-03-13 17:04:19 +08:00
你这同事可以被优化掉了,这是啥代码啊
index90
2019-03-13 17:07:38 +08:00
@seaguest 我这里的使用场景,即使是脏读也是没有问题的。问题是在 Go 里面,pointer 的赋值貌似不是原子操作的。
wweir
2019-03-13 17:08:37 +08:00
在这里,加锁可以解决时序问题,原子性倒是不用担心,golang 的指针操作都是原子的。
之前专门写过文章来聊 golang 里面锁到底是什么:
https://wweir.cc/post/%E6%8E%A2%E7%B4%A2-golang-%E4%B8%80%E8%87%B4%E6%80%A7%E5%8E%9F%E8%AF%AD/
index90
2019-03-13 17:08:45 +08:00
@xkeyideal 代码我重新写的,去掉了其他无关的东西
LANB0
2019-03-13 17:14:12 +08:00
如果 object 为非基本类型的,每次写都涉及到多个字段,你试试看不加锁读会不会出现部分数据已更新部分数据未更新的问题。当然,基本类型不加锁读也会出现一楼说的问题
zhujinliang
2019-03-13 17:17:09 +08:00
sync/atomic 包有个 Value 结构体专门干这个
seaguest
2019-03-13 17:42:15 +08:00
根据官方的说明,应该不是 atomic 的。
但是除非有并发的操作,我们才需要去考虑加锁,否则的话就没有必要。

Go Memory Model:

Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.

To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages.
index90
2019-03-13 17:44:38 +08:00
自己 search 了一下,有人假设一个 64bit 的 pointer,在 write 的时候,可能只写到了一半,就被另外一个线程 read 了,这时候就会 read 到一个不知道是哪里的地址……链接在这: https://stackoverflow.com/questions/41531337/is-a-read-or-write-operation-on-a-pointer-value-atomic-in-golang
官方没有说明指针操作是不是原子的,但是官方只说了一句话:Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.
链接: https://golang.org/ref/mem
index90
2019-03-13 17:47:05 +08:00
@seaguest 瞬间觉得心理压力大,本以为是高级语言,没想到还要关心到那么细
keakon
2019-03-13 17:55:24 +08:00
如果不考虑兼容性和可移植性,只写 64 位的代码就行了。编译器会做内存对齐,在 64 位机器上操作 64 位的指针或整数是原子的。
justfly
2019-03-13 18:06:54 +08:00
@index90 go 的意思是让你但凡并发访问相同数据就用 `sync` 来保证 `serialization`.

Don't be clever. 原话
cloudzhou
2019-03-13 18:07:23 +08:00
你的同事是正确的,当你犹豫是否有并发安全问题的时候,那就采用最保险的方法。
这也不是 Go 的问题,你使用 Java 也有同样的问题的
tulongtou
2019-03-13 18:21:21 +08:00
@keakon 代码还有 64 位的?
keakon
2019-03-13 18:55:50 +08:00
@tulongtou 你编译的时候把 GOARCH 指定为 amd64,然后代码中都假设是 64 位就行了。
lihongjie0209
2019-03-13 19:04:11 +08:00
@cloudzhou 不,You aren't gonna need it, 没有并发问题就不要写并发代码。不然代码没法维护
index90
2019-03-13 19:12:33 +08:00
@lihongjie0209 用 Go 的现在还有非并发的程序吗?
fengjianxinghun
2019-03-13 19:16:48 +08:00
绝大部分语言都有个定律,假如没有明确声明某个行为是并发安全的,那么它就不是。
kkeiko
2019-03-13 19:21:43 +08:00
楼主可以把便以结果打出来看下,理论上说,一次指针的等号赋值确实不是原子的,和 interface 类型没关系。人家用的是读写锁,不是互斥锁,没毛病。
henglinli
2019-03-13 19:28:06 +08:00
并发读写加锁没问题。
个人建议写 go 代码尽量不要用 sync 包,go 的 channel 够用了,如果发现 channel 性能不够用,请考虑重新设计。尽量考虑使用消息传递。如果有能力用 sync/atomic,请忽略以上建议。

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

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

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

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

© 2021 V2EX