在写 go 的时候,你使用 Mutex 的时候使用的是指针还是说没有使用指针,还是随意来?

前两天我收到了下面这样的一个 PR,我突然就想到了这个问题,于是就有了这篇博客。

我一开始的想法

其实我一开始的想法很简单,因为我一直没有使用指针

  1. 在我的某些印象中我曾经记得,使用锁不申明为指针是一个代码规范类似的东西
  2. 大多数的(我看过的一些)源码中,没有见过将锁申明为指针的用法
  3. 但是当时我没有办法回答这个 PR,你总不能说我是一厢情愿吧…需要一个更加合理的解释

仔细分析

上网搜索一番

https://www.reddit.com/r/golang/comments/6uyf16/confusion_about_mutex_and_reference/

很多类似的问题都在问(你不用点开,只是举个例子)

问题关键

sync.Mutex 这个东西不能被 copy!(这个我之前也是知道的,毕竟都分析过源码了)

刨根问底

虽然这个锁不能被拷贝,那么就应该被申明为指针防止拷贝出现问题吗?

别慌,先写个例子测测看

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
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

在什么时候会使用锁呢?一般是不是有一个单例对象要控制,这个对象或者某个操作要控制并发的时候用对吧。

那什么时候会复制对象呢?那么这个对象一定就不是个单例对不对?(注意这里是复制对象,而不是创建指针对象从而复制指针)

1
2
3
4
5
c2 := Config2{
Mutex: &sync.Mutex{},
Name: "2",
}
cc2 := c2

这个写法就已经很古怪了,你复制了这个对象,并且用了同一把锁,那么问题来了:

  • 你的想法究竟是 cc2 锁的时候 c2 也要被锁住?=> 如果是这一种,那么就不应该将锁申明在对象内部。

  • 还是 cc2 锁的时候 c2 不要被锁住?=> 如果是这一种,既不能将锁申明为指针,也能进行拷贝,而应该重新申明一个对象,进行对象其他值的赋值操作。

结论

所以我的结论很明显,不应该申明为指针,申明指针容易在不经意间导致意外。

如果担心拷贝锁的问题,可以使用 go vet 进行分析,现在很多 go 的代码静态分析工具也都提供了这个功能的,其他的也可以。

当然这是我的个人观点,因为语法本身没有错,可能会在一些特殊情况下真的有用到这样的写法~如果我

感谢

在我疑惑自己的想法的时候感谢群里大佬的肯定和指点。

同时也感谢提出这个 PR 的同学,让我更加深刻的学会了这个知识点。

你们遇到问题也要刨根问底哦!不要放过任何一个小问题!