背景
最近看 Go 标准库源码时经常遇到禁止拷贝对象的使用场景,比如当我们使用 strings.Builder 或者 sync.Pool 对象的时候会被禁止拷贝,这是如何实现的呢?
主要有以下两种方式:
方式一:手动检查
这种需要我们在运行时通过 copyCheck 方法来检查是否发生了拷贝操作,我们看下 strings.Builder 的实现方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 结构体
type Builder struct {
addr *Builder // addr 保存了 Builder 对象的地址
buf []byte
}
func (b *Builder) copyCheck() {
if b.addr == nil { // 首次调用 copyCheck 方法时保存自身地址
b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b { // 非首次调用 copyCheck 方法时比较当前对象的地址是否与 addr 相等,不相等则发生 panic
panic("strings: illegal use of non-zero Builder copied by value")
}
}
//执行写方法时,调用 copyCheck 进行拷贝检查
func (b *Builder) Write(p []byte) (int, error) {
b.copyCheck()
// ...
}
|
需要注意的是,strings.Builder 只有在每次执行写方法时才会调用 copyCheck 进行拷贝检查,所以在调用任何写入方法之前是可以进行拷贝的,此时还未进行检查。
关于 strings.Builder的源码分析,可以点击链接查看。
方式二:noCopy
noCopy 是 go1.7 开始引入的一个静态检查机制,它不仅仅工作在运行时或标准库,同时也对用户代码有效。
我们看下标准库 sync.Pool 是如何使用 noCopy 实现禁止拷贝操作的:
1
2
3
4
|
type Pool struct {
noCopy noCopy
//忽略其他字段
}
|
因为 Pool 不希望被复制,所以结构体里有一个 noCopy 的字段,使用 go vet 工具可以检测到用户代码是否复制了 Pool。
noCopy 是如何实现的呢?
1
2
3
4
5
6
7
8
9
|
// noCopy 用于嵌入一个结构体中来保证其第一次使用后不会被复制
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
|
实现非常简单,用户只需实现这样的不消耗内存、仅用于静态分析的结构,来保证一个对象在第一次使用后不会发生复制。
我们做个测试看下效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
type User struct {
noCopy
Name string
}
func main() {
u := User{Name: "maratrix"}
fmt.Println(u) //这里发生了拷贝
}
$ go vet main.go
$ ./main.go:17:14: call of fmt.Println copies lock value: command-line-arguments.User
|
可以看出,使用 go vet 检查时会发生错误。 那能否可以正常编译运行呢?我们试一下:
1
2
|
$ go run main.go
$ {{} maratrix}
|
编译正常。
所以这里要注意:noCopy 只有在使用 go vet 检查才会报错,但仍可以正常编译运行。
小结
主要对笔者最近阅读 Go 源码时遇到的两种禁止对象拷贝的实现方式做个记录总结:
- 手动实现,需要在方法中手动触发 copyCheck 方法,发生拷贝会 panic。
- noCopy,通过在结构体重引入 noCopy,然后使用 go vet 进行检查,发生拷贝仍可以正常编译。
参考
深度解密 Go 语言之 sync.pool
标准库 sync.noCopy 源码
标准库 strings.Builder 源码
标准库 sync.Pool 源码