个人观点,大部分情况下使用指针接收器都是更好的选择
go里面的接收器本质上是一个语法糖,在编译阶段由编译器给你展开成普通函数,比如
func(foo Foo)Bar()string ==> func Bar(foo Foo) string // 值接收器
func(foo *Foo)Bar()string ==> func Bar(foo *Foo) string // 指针接收器
所以我们可以看到,当我们调用值接收器方法时,在Bar的函数栈上传递的是一个值的拷贝,这就带来了一些弊端
1.对于大结构体,拷贝这块内存需要更长的cpu时钟周期
2.因为操作的对象是拷贝的副本,结构体中一些需要共享内存的字段会失效,诸如sync.Mutex这种依靠原子变量去实现协程间同步的机制就会因为数据拷贝导致操作的不是同一个变量使得其失去锁语义。
3.当你构建出了一个包含比较多业务方法的结构体时,需要多个接受者方法配合完成一个动作,那么这几个接受者方法在调用时每次都会发生值拷贝,联系第一点会带来比较大的性能损失。
4.失去引用语义,当你想通过某些方法更新结构体的属性字段时,因为操作的是结构体在Bar函数栈上的拷贝,所以无法达成预定目标。
在下面的例子里,我们可以看到每次调用Bar接受者的地址都在变化。
type Foo struct {
bar string
}
func (foo Foo) Bar() string {
fmt.Println(unsafe.Pointer(&foo))
return foo.bar
}
func (foo *Foo) Bar1() string {
fmt.Println(unsafe.Pointer(foo))
return foo.bar
}
func TestBar(t *testing.T) {
var foo Foo
fmt.Println("BAR")
foo.Bar()
foo.Bar()
foo.Bar()
foo.Bar()
foo.Bar()
foo.Bar()
fmt.Println("BAR1")
foo.Bar1()
foo.Bar1()
foo.Bar1()
foo.Bar1()
foo.Bar1()
foo.Bar1()
}
/*
打印
BAR
0xc0001e5930
0xc0001e5940
0xc0001e5950
0xc0001e5960
0xc0001e5970
0xc0001e5980
BAR1
0xc0001e5920
0xc0001e5920
0xc0001e5920
0xc0001e5920
0xc0001e5920
0xc0001e5920
*/
使用值接收器的好处在于当我们位于Bar函数栈中时,直接访问拷贝到栈上变量的速度比通过指针间接访问变量的速度要快(通过指针间接访问需要做多一次寄存器和访存操作)。当我们需要定义一些比较小的结构体或者是原始类型的别名,且不需要引用语义的时候,可能会比较适合使用值接收器。
比如
type Counter int
func (c Counter) Add(i int) Counter {
return Counter(int(c)+i)
}