“ 指针方法使用场景”
在golang结构体使用中,我们会经常用到值方法和指针方法,呢这两种方法的使用场景都有什么呢?先来看一段代码:
func main() {
cat := New("little pig", "American Shorthair", "cat")
cat.SetName("monster") // (&cat).SetName("monster")
fmt.Printf("The cat: %s\n", cat)
cat.SetNameOfCopy("little pig")
fmt.Printf("The cat: %s\n", cat)
}
type Cat struct {
name string // 名字。
scientificName string // 学名。
category string // 动物学基本分类。
}
//构造一个cat实例
func New(name, scientificName, category string) Cat {
return Cat{
name: name,
scientificName: scientificName,
category: category,
}
}
//传指针设置cat名字
func (cat *Cat) SetName(name string) {
cat.name = name
}
//传入值
func (cat Cat) SetNameOfCopy(name string) {
cat.name = name
}
func (cat Cat) String() string {
return fmt.Sprintf("%s (category: %s, name: %q)",
cat.scientificName, cat.category, cat.name)
}
在这个例子中,我们为Cat设置了两个方法,SetName是传指针的方法,SetNameOfCopy是传值的方法。⽅法SetName的接收者类型是Cat。Cat左边再加个代表的就是Cat类型的指针类型。我们通过运行上面的例子可以得出,值⽅法的接收者是该⽅法所属的那个类型值的⼀个副本。⽽指针⽅法的接收者,是该⽅法所属的那个基本类型值的指针值的⼀个副本。我们在这样的⽅法内对该副本指向的值进⾏修改,却⼀定会体现在原值上。
在日常开发过程中
1)如果仅仅是读取结构体变量,可以不使用指针,直接传递引用即可。不用指针是值传递,基本就不会走gc,缺点是导致整个struct发生内存拷贝,当然被编译器识别为inline函数就什么都不会发生,会很快。当你函数输入/返回参数是比较简单的结构,例如int float,请使用值传递吧。
题外话:什么是golang的内联函数?
栈分配内存会比堆分配高效地多,那么,我们就会希望对象能尽可能被分配在栈上。在Go中,一个goroutine会有一个单独的栈,栈又会包含多个栈帧,栈帧是函数调用时在栈上为函数所分配的区域。但其实,函数调用是存在一些固定开销的,例如维护帧指针寄存器BP、栈溢出检测等。因此,对于一些代码行比较少的函数,编译器倾向于将它们在编译期展开从而消除函数调用,这种行为就是内联。如果不想全局范围内禁止优化,则可以在函数定义时添加 //go:noinline 编译指令来阻止编译器内联函数。
2)需要修改实例就用指针,指针就是类似引用传递,出作用域会走gc,当然也不是绝对,比如inline函数返回指针就不一定会导致堆分配,golang中引用类型的全局变量内存分配在堆上,值类型的全局变量分配在栈上,内置的new和make,map,slice等本身就分配在堆上就必然走gc。gc对于密集型计算服务的后果就是大量cpu计算都消耗在gc上,严重影响性能。
题外话:什么是堆栈?
堆(Heap)在编程中,堆(Heap)是应用程序在运行的时候请求操作系统分配给自己内存,以 C/C++ 程序为例,使用时需要我们主动申请(通过 malloc/New),用完主动释放,或者是程序结束时由操作系统回收,会产生内存碎片。
栈(Stack)栈(Stack)是计算机内存的特定区域,一般 CPU 自动分配释放,数据结构特点是先进先出(FIFO—first in first out)。由于 CPU 可以高效组织内存,读写栈变量会非常快,并且不会产生内存碎片。
堆(Heap)和栈(Stack)的区别
-
栈一般由操作系统来分配和释放,堆由程序员通过编程语言来申请创建与释放;
-
栈用来存放函数的参数、返回值、局部变量、函数调用时的临时上下文等,只要是局部的、占用空间确定的数据,一般都存放在 Stack,否则就放在 Heap;
-
栈的访问速度相对比堆快;
-
一般来说,Stack 是线程独占的,Heap 是线程共用的;
-
Stack 创建的时候,大小是确定的,Heap 的大小是不确定的,可以自由增加;
C/C++ 线程中,多数架构上默认线程栈大小都在 2MB ~ 4MB 左右,如果程序同时运行几百个甚至几千个线程,会占用大量的内存空间并带来其他的额外开销,Go 语言在设计时认为执行上下文是轻量级的,所以它在用户态实现 Goroutine 作为执行上下文,最小的栈空间只有 2KB。所以非常高效。
总结来说,指针多了并不一定增加性能,有可能增加gc的压力,从而降低性能,需要修改实例就用指针,不需要就用非指针版本。如果不确定用什么的时候,可以使用指针receive
高性能 Go 服务中的分配效率
https://segment.com/blog/allocation-efficiency-in-high-performance-go-services/
Go语言中的内联函数
https://segmentfault.com/a/1190000040399875
go.dev
https://go.dev/doc/effective_go#pointers_vs_values
uber_go_guide_cn
https://github.com/xxjwxc/uber_go_guide_cn#%E6%8E%A5%E6%94%B6%E5%99%A8-receiver-%E4%B8%8E%E6%8E%A5%E5%8F%A3
luozhiyun`s Blog
https://www.luozhiyun.com/archives/211
文章评论