前言
在写 go 的时候,你使用 Mutex 的时候使用的是指针还是说没有使用指针,还是随意来?
前两天我收到了下面这样的一个 PR,我突然就想到了这个问题,于是就有了这篇博客。
我一开始的想法
其实我一开始的想法很简单,因为我一直没有使用指针
- 在我的某些印象中我曾经记得,使用锁不申明为指针是一个代码规范类似的东西
- 大多数的(我看过的一些)源码中,没有见过将锁申明为指针的用法
- 但是当时我没有办法回答这个 PR,你总不能说我是一厢情愿吧...需要一个更加合理的解释
仔细分析
上网搜索一番
https://www.reddit.com/r/golang/comments/6uyf16/confusion_about_mutex_and_reference/
很多类似的问题都在问(你不用点开,只是举个例子)
问题关键
sync.Mutex
这个东西不能被 copy!(这个我之前也是知道的,毕竟都分析过源码了)
刨根问底
虽然这个锁不能被拷贝,那么就应该被申明为指针防止拷贝出现问题吗?
别慌,先写个例子测测看
package main
import (
"fmt"
"sync"
)
type Config1 struct {
sync.Mutex
Name string
}
type Config2 struct {
*sync.Mutex
Name string
}
func main() {
c1 := Config1{Name: "1"}
cc1 := c1
fmt.Println(cc1.Name)
cc1.Lock()
cc1.Unlock()
c2 := Config2{
Mutex: &sync.Mutex{},
Name: "2",
}
cc2 := c2
fmt.Println(cc2.Name)
cc2.Lock()
cc2.Unlock()
}
上面这个跑起来没问题,但是要注意的是,如果使用指针,你就必须对它初始化,否则会空指针。
看起来好像 copy 没问题啊?难道?让我 vet 看看
果然有问题,因为有拷贝。
但是结论我认为恰恰相反!!
我的结论
就应该不应该申明为指针
原因 1
假设你申明为了指针,go vet 就不会报错,那么其实你在使用的时候,在不知情的情况下你就会“复制”这个锁
原因 2
在什么时候会使用锁呢?一般是不是有一个单例对象要控制,这个对象或者某个操作要控制并发的时候用对吧。
那什么时候会复制对象呢?那么这个对象一定就不是个单例对不对?(注意这里是复制对象,而不是创建指针对象从而复制指针)
c2 := Config2{
Mutex: &sync.Mutex{},
Name: "2",
}
cc2 := c2
这个写法就已经很古怪了,你复制了这个对象,并且用了同一把锁,那么问题来了:
你的想法究竟是 cc2 锁的时候 c2 也要被锁住?=> 如果是这一种,那么就不应该将锁申明在对象内部。
还是 cc2 锁的时候 c2 不要被锁住?=> 如果是这一种,既不能将锁申明为指针,也能进行拷贝,而应该重新申明一个对象,进行对象其他值的赋值操作。
结论
所以我的结论很明显,不应该申明为指针,申明指针容易在不经意间导致意外。
如果担心拷贝锁的问题,可以使用 go vet 进行分析,现在很多 go 的代码静态分析工具也都提供了这个功能的,其他的也可以。
当然这是我的个人观点,因为语法本身没有错,可能会在一些特殊情况下真的有用到这样的写法~如果我
感谢
在我疑惑自己的想法的时候感谢群里大佬的肯定和指点。
同时也感谢提出这个 PR 的同学,让我更加深刻的学会了这个知识点。
你们遇到问题也要刨根问底哦!不要放过任何一个小问题!
有疑问加站长微信联系(非本文作者)