Go中Struct的指针方法和非指针方法
在go中,我们可以为结构体绑定方法,使某个方法属于某个函数,但是却有两种不一样的方法
例如:
type T struct {
Name string
}
func (t T) M1() {
t.Name = "name1"
}
func (t *T) M2() {
t.Name = "name2"
}
这两种方法看起来都是对Name进行了修改,我们来运行一下
func main(){
t:=T{Name: "name"}
fmt.Println("T:",t.Name)
t.M1()
fmt.Println("M1:",t.Name)
t.M2()
fmt.Println("M2:",t.Name)
}
输出:
T: name
M1: name
M2: name2
可以看到M1方法并不能对原对象进行修改,这是因为M1是值引用,当运行函数的时候,会复制一个相同的对象来执行函数,相当于执行M1(t)
M2(&t)
,M1修改的是复制的对象而不是原对象,因此如果期望对原对象进行修改,则必须采用指针方法。
但是如果t是指针呢?
func main(){
t:=&T{Name: "name"}
fmt.Println("T:",t.Name)
t.M1()
fmt.Println("M1:",t.Name)
t.M2()
fmt.Println("M2:",t.Name)
}
输出:
T: name
M1: name
M2: name2
结果是一样的。
这是因为,运行t2.M1的时候,会取t2地址的值复制一遍再传递给M1.
但是请注意,T是拥有M1方法的,*T是拥有M1,M2这两种方法的。
如果运行
type Intf interface {
M1()
M2()
}
func main(){
t:=T{Name: "name"}
var t2 Intf = t
fmt.Println("T:",t.Name)
t2.M1()
fmt.Println("M1:",t.Name)
t2.M2()
fmt.Println("M2:",t.Name)
}
将报错
.\funcTest.go:25:6: cannot use t (type T) as type Intf in assignment:
T does not implement Intf (M2 method has pointer receiver)
一个小案例
package main
import "sync"
type MyMap struct {
m map[int]int
lock sync.Mutex
}
func (myMap *MyMap) Set(i int) {
myMap.lock.Lock()
defer myMap.lock.Unlock()
myMap.m[i]=i
}
func (myMap MyMap) Get(i int) int {
myMap.lock.Lock()
defer myMap.lock.Unlock()
return myMap.m[i]
}
func main(){
m:=&MyMap{m:make(map[int]int)}
wg:=sync.WaitGroup{}
for i:=0;i<100;i++{
wg.Add(1)
go func(num int){
defer wg.Done()
// 交替执行Set,Get
if num%2==0{
m.Set(num)
}else{
m.Get(num)
}
}(i)
}
wg.Wait()
}
代码很简单,一个并发安全的map,看起来没有任何问题,但是当运行代码时,非常可能将会报错fatal error: all goroutines are asleep - deadlock!
发生了死锁,为什么呢?不是最后都Unlock()
了吗?其实可以注意到func (myMap MyMap) Get(i int)
,这是非指针引用,而sync.Mutex
也不是指针对象,因此当调用Get()的时候将会复制一份新的结构体,因此这个锁就已经不是原来的锁了,也就是说加锁失败了。但是这其实也不是发生死锁的原因(笑),这会导致另一个错误fatal error: concurrent map read and map write
,对一个map同时读写。出现死锁的原因是,当进行值拷贝的时候,同时也会拷贝锁的状态!!!,也就是说我们可能拷贝了一份上锁了的锁过来,然后我们又在等待锁的释放,但是并没有人可以对其进行释放,因此就进入了死锁状态。
所以请在编写代码是时候习惯性的采用指针方法,一来提高性能(不用进行数据的拷贝),二来避免出现奇奇怪怪的BUG。