一个报错引起的思考。
1.问题 1 2 3 4 func ModelToResponse (user model.User) proto.UserInfoResponse { return userInfoRsp }
在实现以上代码时,Goland 有以下 Warning 信息。
1 Return copies the lock value: type 'proto.UserInfoResponse' contains 'protoimpl.MessageState' contains 'sync.Mutex' which is 'sync.Locker'
虽然不处理可以正常运行,但是本着学习的态度一探究竟。
2.原因 UserInfoResponse.protoimpl.MessageState
包含锁 sync.Mutex
。为了保障并发安全,不建议直接通过值传递的方式返回含锁对象,而应该使用指针传递的方式。比如,
1 2 3 4 func ModelToResponse (user model.User) *proto.UserInfoResponse { return &userInfoRsp }
具体原因是:值拷贝过程中,也会复制锁(也就是存在两把锁),对于同一临界资源,两个协程分别使用两把锁起不到互斥作用。
3.分析 下面以一个例来具体分析:S 中含有一个指针和一个锁,使用锁可以保障指针指向内存的并发安全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport ( "fmt" "sync" "time" )type S struct { p *int mutex sync.Mutex }func NewS () S { a := 0 A := S{p: &a} return A }func main () { A := NewS() B := A go func () { for i := 0 ; i < 10000 ; i++ { A.mutex.Lock() *A.p++ A.mutex.Unlock() } }() go func () { for i := 0 ; i < 10000 ; i++ { B.mutex.Lock() *B.p++ B.mutex.Unlock() } }() time.Sleep(time.Second * 2 ) fmt.Println(*A.p, *B.p) }
这个份代码会报两个 warning,分别是第 17 行 return A
和第 22 行 B := A
两个地方,报错意思是一样的,均不建议使用值传递的方式拷贝带锁的对象。
1 2 line 17:Return copies the lock value: type 'S' contains 'sync.Mutex' which is 'sync.Locker' line 22:Variable declaration copies a lock value to 'B' : type 'S' contains 'sync.Mutex' which is 'sync.Locker'
以上述代码为例,由于是值传递,对象中的锁也会被拷贝,A.Mutex
和 B.Mutex
是两个不同的锁,而 A.p
和 B.p
是指向同一块内存,对于这块内存,两把锁无法起到互斥作用。
所以,这块内存的数据 a,在两个 goroutine 中一共进行 20000 次加法之后,结果仍小于 20000。
4.修正 1 2 3 4 5 func NewS () *S { a := 0 A := S{p: &a} return &A }
只需改为指针传递即可。这样 A.Mutex
和 B.Mutex
是同一把锁,在并发场景下,对于 a 的加锁是有效的。
最后加法后的结果为 20000。