Go语言变量的生命周期
变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。对于在包级别(函数外部)声明的变量来说,它们的生命周期和整个程序的运行周期是一致的,而相比之下,局部变量的生命周期则是动态的,每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能会被回收。函数的参数变量和返回值变量都是局部变量,它们在函数每次被调用的时候创建。
例如,下面摘录的部分代码片段:
那么Go语言的自动垃圾收集器是如何知道一个变量何时可以被回收的呢?这里我们可以避开完整的技术细节,基本的实现思路是,遍历当前运行程序中变量(包括全局变量与局部变量)的指针和引用,查看是否可以通过某个指针或引用找到该变量,如果找不到则说明该变量已经不再被使用,可以被自动垃圾收集器回收。
因为一个变量的有效周期只取决于是否可达,所以一个循环内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可以在函数返回之后依然存在。
编译器会自动选择在栈上还是在堆上分配局部变量的内存空间,使用 var 或 new 关键字声明变量并不会影响编译器的选择。
相反,当函数 g 返回时,变量 *y 将是不可达的,也就是说变量 *y 可以马上被回收,因此,*y 并没有从函数 g 中逃逸,编译器可以选择在栈上分配 *y 的存储空间(也可以选择在堆上分配,然后由Go语言的 GC 回收这个变量的内存空间)。
在实际开发的过程中,并不需要刻意的实现变量的逃逸行为,因为变量的逃逸需要额外分配内存空间,同时对程序的性能也会有细微的影响。
Go语言的自动垃圾收集器可以帮助我们完成垃圾回收的工作,但并不是代表着我们可以完全不用考虑内存的优化了,虽然Go语言可以帮我们分配和释放内存空间,但是想要编写高性能的程序我们依然需要了解变量的声明周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收,从而可能影响程序的性能。
例如,下面摘录的部分代码片段:
for t := 0.0; t < cycles*2*math.Pi; t += res { x := math.Sin(t) y := math.Sin(t*freq + phase) img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex) }提示:函数的右小括号
)
也可以另起一行书写,同时为了防止编译器在行尾自动插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗号。像下面这样:for t := 0.0; t < cycles*2*math.Pi; t += res { x := math.Sin(t) y := math.Sin(t*freq + phase) img.SetColorIndex( size+int(x*size+0.5), size+int(y*size+0.5), blackIndex, // 最后插入的逗号不会导致编译错误,这是Go编译器的一个特性 ) // 小括号另起一行缩进,和大括号的风格保存一致 }在每次循环的开始会创建临时变量 t,然后在每次循环迭代中创建临时变量 x 和 y。
那么Go语言的自动垃圾收集器是如何知道一个变量何时可以被回收的呢?这里我们可以避开完整的技术细节,基本的实现思路是,遍历当前运行程序中变量(包括全局变量与局部变量)的指针和引用,查看是否可以通过某个指针或引用找到该变量,如果找不到则说明该变量已经不再被使用,可以被自动垃圾收集器回收。
因为一个变量的有效周期只取决于是否可达,所以一个循环内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可以在函数返回之后依然存在。
编译器会自动选择在栈上还是在堆上分配局部变量的内存空间,使用 var 或 new 关键字声明变量并不会影响编译器的选择。
var global *int func f() { var x int x = 1 global = &x } func g() { y := new(int) *y = 1 }函数 f 里的变量 x 必须在堆上分配,因为它在函数退出后依然可以通过包级别的 global 变量找到,虽然它是在函数内部定义的,用Go语言的术语说,这个局部变量 x 从函数 f 中逃逸了。
相反,当函数 g 返回时,变量 *y 将是不可达的,也就是说变量 *y 可以马上被回收,因此,*y 并没有从函数 g 中逃逸,编译器可以选择在栈上分配 *y 的存储空间(也可以选择在堆上分配,然后由Go语言的 GC 回收这个变量的内存空间)。
在实际开发的过程中,并不需要刻意的实现变量的逃逸行为,因为变量的逃逸需要额外分配内存空间,同时对程序的性能也会有细微的影响。
Go语言的自动垃圾收集器可以帮助我们完成垃圾回收的工作,但并不是代表着我们可以完全不用考虑内存的优化了,虽然Go语言可以帮我们分配和释放内存空间,但是想要编写高性能的程序我们依然需要了解变量的声明周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收,从而可能影响程序的性能。
所有教程
- socket
- Python基础教程
- C#教程
- MySQL函数
- MySQL
- C语言入门
- C语言专题
- C语言编译器
- C语言编程实例
- GCC编译器
- 数据结构
- C语言项目案例
- C++教程
- OpenCV
- Qt教程
- Unity 3D教程
- UE4
- STL
- Redis
- Android教程
- JavaScript
- PHP
- Mybatis
- Spring Cloud
- Maven
- vi命令
- Spring Boot
- Spring MVC
- Hibernate
- Linux
- Linux命令
- Shell脚本
- Java教程
- 设计模式
- Spring
- Servlet
- Struts2
- Java Swing
- JSP教程
- CSS教程
- TensorFlow
- 区块链
- Go语言教程
- Docker
- 编程笔记
- 资源下载
- 关于我们
- 汇编语言
- 大数据
- 云计算
- VIP视频