目录

no copy机制

sync包下面经常出现"XXX must not be copied after first use.",然后下面就有一个noCopy

什么是noCopy ?

如果结构体对象包含指针字段,当该对象被拷贝时,会使得两个对象中的指针字段变得不再安全。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
type S struct {
 f1 int
 f2 *s
}

type s struct {
 name string
}

func main() {
 mOld := S{
  f1: 0,
  f2: &s{name: "mike"},
 }
 mNew := mOld //拷贝
 mNew.f1 = 1
 mNew.f2.name = "jane"

 fmt.Println(mOld.f1, mOld.f2) //输出:0 &{jane}
}

修改nNew字段的值会把mOld字段的值修改掉,这就会引发安全问题。

如果保证noCopy

runtime checking

copy检查

1
2
3
4
5
6
7
func main() {
	var a strings.Builder
	a.Write([]byte("a"))
	b := a
	b.Write([]byte("b"))
}
// 运行报错: panic: strings: illegal use of non-zero Builder copied by value

报错信息来自于strings.BuildercopyCheck

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Do not copy a non-zero Builder.
type Builder struct {
	addr *Builder // of receiver, to detect copies by value
	buf  []byte
}

func (b *Builder) Write(p []byte) (int, error) {
	b.copyCheck()
	b.buf = append(b.buf, p...)
	return len(p), nil
}

func (b *Builder) copyCheck() {
	if b.addr == nil {
		// This hack works around a failing of Go's escape analysis
		// that was causing b to escape and be heap allocated.
		// See issue 23382.
		// TODO: once issue 7921 is fixed, this should be reverted to
		// just "b.addr = b".
		b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
	} else if b.addr != b {
		panic("strings: illegal use of non-zero Builder copied by value")
	}
}

Builder中,addr是一个指向自身的指针。如果a复制给b,那么ab本身是不同的对象。因此b.addr实际上是指向a,就会导致panic.

sync.Cond中的copy检查

1
2
3
4
5
6
7
8
9
type copyChecker uintptr

func (c *copyChecker) check() {
	if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
		!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
		uintptr(*c) != uintptr(unsafe.Pointer(c)) {
		panic("sync.Cond is copied")
	}
}

当被拷贝后,uintptr(*c)和uintptr(unsafe.Pointer(c))的值是不同的,通过uint对象的原子比较方法CompareAndSwapUintptr将返回false,它证明了对象a被copy过,从而调用panic保护sync.Cond不被复制。

go vet checking

上面的两个都是在程序编译时,runtime进行检查的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// noCopy may be embedded into structs which must not be copied
// after the first use.
//
// 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() {}

sync包下的其他的对象如Pool、WaitGroup、Mutex、Map等,它们都存在copy检查机制。

通过go vet进行copy检查。

那么我们可以参考Go源码的noCopy,实现调用不能拷贝

1
2
3
4
5
6
7
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
type MyType struct {
   noCopy noCopy
   ...
}