Go struct 嵌套不可复制类型

3 阅读3分钟

struct 嵌套「不可复制」类型

struct 是我们写 Go 必然会用到的关键字,不过当 struct 遇上一些比较特殊类型的时候,例如: Mutex,你注意过你的程序是否依然正常吗?

1.一般情况:

func main() {
 var mux sync.Mutex
 l := mux
 l.Lock()
 l.Unlock()
}

2.嵌套在 struct 中,struct 变量的值之间互相赋值:

type URL struct {
 IP  string
 mux sync.RWMutex
}

func main() {
 var url1 URL
 url2 := url1
}

当 struct 嵌套「不可复制」类型时,就需要开始小心了。当 struct 嵌套层次过深或者 struct 变量随着值传递对外扩散时,这个时候就会变得不可控。

3.嵌套在 struct 中,struct 变量的值传递作为返回值:

type URL struct {
 IP  string
 mux sync.RWMutex
}

func (c *URL) Clone() URL {
 newUrl := URL{}
 newUrl.IP = c.IP
 return newUrl
}

4.嵌套在 struct 中,struct 变量的值传递作为方法接收器:

type URL struct {
 IP  string
 mux sync.RWMutex
}

func (c URL) Clone()  {
}

当写出上述代码的时候,由于目前的 Goland,Vscode 都会集成 go vet 的相关功能,如果你强迫症比较严重的话,你就能发现有相关提示:Variable declaration copies a lock value ... is sync.Locker 。Locker 接口代表一个可以加锁和解锁的对象。

不能复制的原因

以 Mutex 为例:

type Mutex struct {
  state int32
  sema  uint32
}

我们使用 Mutex 是为了不同 goroutine 之间共享某个变量,所以需要让这个变量做到能够互斥,不然该变量就会被互相被覆盖。

Mutex 底层是由 state、sema 控制的,当 Mutex 变量被复制时,Mutex 的 state、ema 当时的状态也被复制走了,但是由于不同 goroutine 之间的 Mutex 已经不是同一个变量了,这样就会造成要么某个 goroutine 死锁或者不同 goroutine 共享的变量达不到互斥。

sync 包使用的注意事项:

// Values containing the types defined in this package should not be copied.
// 不应复制那些包含了此包中类型的值
 
// src/sync/mutex.go
// A Mutex must not be copied after first use. 

// src/sync/rwmutex.go
// A RWMutex must not be copied after first use.

// src/sync/cond.go
// A Cond must not be copied after first use.

// sync/waitgroup.go
// A WaitGroup must not be copied after first use.

 ... ...

那些 sync 包中的类型的实例在首次使用后被复制得到的副本一旦再被使用将导致不可预期的结果,为此在使用 sync 包中的类型的时候,我们推荐通过闭包方式或传递类型实例(或包裹该类型的类型实例)的地址(指针)的方式进行,这是使用 sync 包最值得注意的事项。

struct 如何与「不可复制」的类型一块使用?

由上面可以看到不只是 sync 相关类型变量自身不能被复制,而且 struct 嵌套「不可复制」类型变量时,同样也不能被复制。但是如果我将嵌套的不可复制变量改成指针类型变量呢,是不是就解决了不能复制的问题 ?

type URL struct {
 IP  string
 mux *sync.RWMutex
}

这样确实解决了上述的不能复制问题。但也引出了另外一个问题。众所周知 Go 没有构造函数, 这就导致我们使用 URL 的时候都需要先去初始化 RWMutex,不然就会造成同样很严重的空指针问题,这个问题同样很棘手,也许哪个位置就忘了初始化这个 RWMutex。

根据 google groups 的讨论 How to copy a struct which contains a mutex ? 以及查看 Kubernets 的相关源码,发现大家的观点基本上都是一致的,都不会去选用 struct 去嵌套指针类型的变量,由此不建议 struct 去嵌套「不可复制」的指针类型变量。最重要的原因:没有一个工具能去准确的检测空指针。

所以一般情况下,当 struct 嵌套了「不可复制」类型的变量时,都需要传递的是 struct 类型变量的指针,如:

type URL struct {
 IP  string
 mux sync.RWMutex
}

func (c *URL) Clone() *URL {
 newUrl := &URL{}
 newUrl.IP = c.IP
 return newUrl
} 

References
groups.google.com/g/golang-nu…
github.com/kubernetes/…