背景:今天心血来潮用 golangci-lint 检查自己的代码,发现了一个误用kitex中结构体的警告
internal/user/dao/user.go:45:11: copylocks: return copies lock value: TTMS/kitex_gen/user.User contains
google.golang.org/protobuf/internal/impl.MessageState contains sync.Mutex (govet)
return u, nil
第一反应是赶紧看怎么解决,结果发现返回一个指针类型就没有警告了,后来仔细想了一下,发现确实是个之前没注意到的小问题,但关键时刻也可能念成大祸!
如果结构体中的锁被复制了(即返回的结构体是原始结构体的副本),则在使用该结构体时会出现问题。这是因为 sync.Mutex
锁是一个互斥量,它的主要作用是用于多个 goroutine 对共享资源进行互斥访问。如果一个结构体的副本中包含一个锁的副本,则在使用该结构体的时候,可能会导致两个 goroutine 同时访问同一个共享资源,从而破坏了锁的互斥性。
一句话总结就是:一个锁的复制出来的副本和原来是不一样的,但是把锁对象的指针传出去,所有能引用该指针的对象拿到的都是同一把锁。
比如,以下示例:
import (
"fmt"
"sync"
"time"
)
type MyStruct struct {
mutex sync.Mutex
data int
}
// 1.返回 *MyStruct 时,s1 s2 用的是同一把锁
func getMyStruct() *MyStruct {
return &MyStruct{data: 0}
}
// 2.返回 MyStruct 时,s2 用的是 s1中锁的副本,本质上是两把锁
//
// func getMyStruct() MyStruct {
// return MyStruct{data: 0}
// }
func main() {
s1 := getMyStruct()
s2 := s1
go func() {
for i := 0; i < 10000; i++ {
s1.mutex.Lock()
s1.data++
s1.mutex.Unlock()
}
}()
go func() {
for i := 0; i < 10000; i++ {
s2.mutex.Lock()
s1.data++
s2.mutex.Unlock()
}
}()
// 等待 goroutine 结束
time.Sleep(1 * time.Second)
fmt.Println(s1.data)
// 1.当两个协程使用同一把锁时 结果为 20000
// 2.当两个协程使用不同锁时(相当直接没加锁) 结果一般不足 20000
}
在上面的示例中,我们 s1
传给 s2
,并在两个 goroutine 中对它们进行修改。
传递锁的指针和锁的副本两种情况分别对应 1,2 两种处理。