Skip to content

Latest commit

 

History

History
116 lines (87 loc) · 3.89 KB

nocopy.md

File metadata and controls

116 lines (87 loc) · 3.89 KB

简介

Go中一些数据结构是no copy的。这里面的no copy指的是第一次使用之后,该变量不能赋值给另外一个变量来使用,或者通过函数值传递来使用。对于这些数据结构,Go源码中一般都会添加must not be copied after first use注释来说明:

➜  go-1.14.13 git:(master) grep -r 'must not be copied after first use' ./src
./src/cmd/go/internal/lockedfile/mutex.go:// must not be copied after first use. The Path field must be set before first
./src/sync/atomic/value.go:// A Value must not be copied after first use.
./src/sync/cond.go:// A Cond must not be copied after first use.
./src/sync/rwmutex.go:// A RWMutex must not be copied after first use.
./src/sync/map.go:// The zero Map is empty and ready for use. A Map must not be copied after first use.
./src/sync/mutex.go:// A Mutex must not be copied after first use.
./src/sync/pool.go:// A Pool must not be copied after first use.
./src/sync/waitgroup.go:// A WaitGroup must not be copied after first use.

对于no copy的结构体不能copy的原因是该结构体的某些字段记录了该结构体的状态信息,或者计数器信息等,该结构体提供的方法会更新这些信息,而这些字段是值引用的,当copy之后,调用新变量的方法不会再更新原来结构体对应的信息。

Go中是如何保证no copy的?

Go中提供两种方法来保证no copy机制:runtime checkinggo vet checking

runtime checking

runtime checking指的在程序运行时检查对象是否进行copy了。Go中对strings.Buildersync.Cond提供了runtime checking。

strings.Builder copy示例:

func main() {
	var s1 strings.Builder
	s1.Write([]byte("a"))
	s2 := s1
	s2.Write([]byte("b"))
}

strings.Builder中no copy runtime检查源码

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 noescape(p unsafe.Pointer) unsafe.Pointer {
	x := uintptr(p)
	return unsafe.Pointer(x ^ 0)
}

func (b *Builder) copyCheck() {
	if b.addr == nil {
		b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
	} else if b.addr != b {
		panic("strings: illegal use of non-zero Builder copied by value")
	}
}

string.Builder进行Write时候,会先进行copyCheck。示例中当s1赋值给s2时候,s2的addr指向的还是s1。s2进行Write时候,copyCheck中会触发逻辑b.addr != b,发生恐慌。

go vet checking

go vet命令提供了静态检查功能。用来编译时候提供检查。只要实现了sync.Locker接口的对象,那么go vet就会检查代码中有没有copy这个对象的逻辑。

相比runtime checkinggo vet checking不会影响程序性能。

sync.Locker定义

type Locker interface {
	Lock()
	Unlock()
}

sync包提供了noCopy类型,它实现了sync.Locker接口

type noCopy struct{}
func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

sync.Pool, sync.WaitGroup, sync.Cond通过内嵌noCopy类型的字段实现sync.Locker接口。

type Pool struct {
    noCopy noCopy
    ...
}

type WaitGroup struct {
    noCopy noCopy
    ...
}

type Cond struct {
    noCopy noCopy
    ...
}

sync.Mutex, sync.RWMutex通过自己实现Lock()Unlock()方法,实现了sync.Locker接口

sync.Map通过内嵌sync.Mutex来实现sync.Locker接口

总结

  • Go中采用runtime checkinggo vet checking两种方法来保证no copy机制
  • Go中stings.Builder, sync.Pool, sync.WaitGroup, sync.Cond, sync.Map, sync.Mutex, sync.RWMutextno copy