Golang 闭包实现原理
闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数 引用环境)。
在解读闭包原理之前我们新看一下闭包的效果是什么样的:
package main
import "fmt"
func main() {
f := f(0)
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
}
func f(i int) func() int {
return func() int {
i
return i
}
}
你认为上述代码的运行结果是什么?这就是一个最简单的闭包应用它的结果为:
1
2
3
引用环境的不同:
package main
import "fmt"
func main() {
fmt.Println("f1:")
f1 := f(0)
fmt.Println(f1())
fmt.Println(f1())
fmt.Println(f1())
fmt.Println("f2:")
f2 := f(10)
fmt.Println(f2())
fmt.Println(f2())
fmt.Println(f2())
}
func f(i int) func() int {
return func() int {
i
return i
}
}
输出:
f1:
1
2
3
f2:
11
12
13
可以看出每一个通过f(i int) 返回的 func都是一个独立的引用环境。
在未深入了解闭包之前我的理解就是相当于一个struct 例如:
func main() {
demo := new(Demo)
fmt.Println(demo.Add())
fmt.Println(demo.Add())
fmt.Println(demo.Add())
}
type Demo struct {
i int
}
func (d *Demo) Add() int {
d.i
return d.i
}
虽然可以实现同样的功能,但是上面的代码我们深知其内部流程可以放心的使用起来,而闭包不了解其运行过程的情况下,始终是无法安心用于生产环境的。
在研究闭包的实现之前我们来看一下Go语言的一个特性,也正是因为这个特性才有了闭包的存在:
func f() *Cursor {
var c Cursor
c.X = 500
noinline()
return &c
}
Cursor是一个struct,这种写法如果在c语言中是不允许出现的,因为变量c是在栈上分配的空间,当函数f结束返回c的地址的时候就失效了(局部变量的使用范围)。但是在go语言规范中有说明,这种写法是合法的。语言会自动识别处这种情况并在堆上分配c的空间,而不是在函数f的栈上。这样就逃逸了局部变量的范围。也就是闭包存在的原因!
那么我们将多于的代码删除掉,转换成汇编看一下吧!
package main
import "fmt"
func main() {
f1 := f(0)
f1()
}
func f(i int) func() int {
return func() int {
i
return i
}
}
汇编:
"".f STEXT size=185 args=0x10 locals=0x28
0x0000 00000 (main.go:8) TEXT "".f(SB), ABIInternal, $40-16
0x0000 00000 (main.go:8) MOVQ (TLS), CX
0x0009 00009 (main.go:8) CMPQ SP, 16(CX)
0x000d 00013 (main.go:8) JLS 175
0x0013 00019 (main.go:8) SUBQ $40, SP
0x0017 00023 (main.go:8) MOVQ BP, 32(SP)
0x001c 00028 (main.go:8) LEAQ 32(SP), BP
0x0021 00033 (main.go:8) FUNCDATA $0, gclocals·40785253dc42ba97df883df3cb2ea22c(SB)
0x0021 00033 (main.go:8) FUNCDATA $1, gclocals·cc5cad6002ade825472fe3ad2912087d(SB)
0x0021 00033 (main.go:8) FUNCDATA $3, gclocals·1e847dfe98917b7bcc7220b0d92d534f(SB)
0x0021 00033 (main.go:8) PCDATA $2, $0
0x0021 00033 (main.go:8) PCDATA $0, $0
0x0021 00033 (main.go:8) MOVQ $0, "".~r1 56(SP)
0x002a 00042 (main.go:8) PCDATA $2, $1
0x002a 00042 (main.go:8) LEAQ type.int(SB), AX
0x0031 00049 (main.go:8) PCDATA $2, $0
0x0031 00049 (main.go:8) MOVQ AX, (SP)
0x0035 00053 (main.go:8) CALL runtime.newobject(SB)
0x003a 00058 (main.go:8) PCDATA $2, $1
0x003a 00058 (main.go:8) MOVQ 8(SP), AX
0x003f 00063 (main.go:8) PCDATA $0, $1
0x003f 00063 (main.go:8) MOVQ AX, "".&i 24(SP)
0x0044 00068 (main.go:8) MOVQ "".i 48(SP), CX
0x0049 00073 (main.go:8) PCDATA $2, $0
0x0049 00073 (main.go:8) MOVQ CX, (AX)
0x004c 00076 (main.go:9) PCDATA $2, $1
0x004c 00076 (main.go:9) LEAQ type.noalg.struct { F uintptr; "".i *int }(SB), AX
0x0053 00083 (main.go:9) PCDATA $2, $0
0x0053 00083 (main.go:9) MOVQ AX, (SP)
0x0057 00087 (main.go:9) CALL runtime.newobject(SB)
0x005c 00092 (main.go:9) PCDATA $2, $1
0x005c 00092 (main.go:9) MOVQ 8(SP), AX
0x0061 00097 (main.go:9) PCDATA $0, $2
0x0061 00097 (main.go:9) MOVQ AX, ""..autotmp_5 16(SP)
0x0066 00102 (main.go:9) LEAQ "".f.func1(SB), CX
0x006d 00109 (main.go:9) PCDATA $2, $0
0x006d 00109 (main.go:9) MOVQ CX, (AX)
0x0070 00112 (main.go:9) PCDATA $2, $1
0x0070 00112 (main.go:9) MOVQ ""..autotmp_5 16(SP), AX
0x0075 00117 (main.go:9) TESTB AL, (AX)
0x0077 00119 (main.go:9) PCDATA $2, $2
0x0077 00119 (main.go:9) PCDATA $0, $3
0x0077 00119 (main.go:9) MOVQ "".&i 24(SP), CX
0x007c 00124 (main.go:9) PCDATA $2, $3
0x007c 00124 (main.go:9) LEAQ 8(AX), DI
0x0080 00128 (main.go:9) PCDATA $2, $-2
0x0080 00128 (main.go:9) PCDATA $0, $-2
0x0080 00128 (main.go:9) CMPL runtime.writeBarrier(SB), $0
0x0087 00135 (main.go:9) JEQ 139
0x0089 00137 (main.go:9) JMP 165
0x008b 00139 (main.go:9) MOVQ CX, 8(AX)
0x008f 00143 (main.go:9) JMP 145
0x0091 00145 (main.go:9) PCDATA $2, $1
0x0091 00145 (main.go:9) PCDATA $0, $0
0x0091 00145 (main.go:9) MOVQ ""..autotmp_5 16(SP), AX
0x0096 00150 (main.go:9) PCDATA $2, $0
0x0096 00150 (main.go:9) PCDATA $0, $4
0x0096 00150 (main.go:9) MOVQ AX, "".~r1 56(SP)
0x009b 00155 (main.go:9) MOVQ 32(SP), BP
0x00a0 00160 (main.go:9) ADDQ $40, SP
0x00a4 00164 (main.go:9) RET
0x00a5 00165 (main.go:9) PCDATA $2, $-2
0x00a5 00165 (main.go:9) PCDATA $0, $-2
0x00a5 00165 (main.go:9) MOVQ CX, AX
0x00a8 00168 (main.go:9) CALL runtime.gcWriteBarrier(SB)
0x00ad 00173 (main.go:9) JMP 145
0x00af 00175 (main.go:9) NOP
0x00af 00175 (main.go:8) PCDATA $0, $-1
0x00af 00175 (main.go:8) PCDATA $2, $-1
0x00af 00175 (main.go:8) CALL runtime.morestack_noctxt(SB)
0x00b4 00180 (main.go:8) JMP 0
这里特意截处f方法的汇编,接下来我们还需要再次定位:
"".f STEXT size=185 args=0x10 locals=0x28
0x0000 00000 (main.go:8) TEXT "".f(SB), ABIInternal, $40-16
0x0000 00000 (main.go:8) MOVQ (TLS), CX
0x0009 00009 (main.go:8) CMPQ SP, 16(CX)
0x000d 00013 (main.go:8) JLS 175
0x0013 00019 (main.go:8) SUBQ $40, SP
0x0017 00023 (main.go:8) MOVQ BP, 32(SP)
0x001c 00028 (main.go:8) LEAQ 32(SP), BP
0x0021 00033 (main.go:8) FUNCDATA $0, gclocals·40785253dc42ba97df883df3cb2ea22c(SB)
0x0021 00033 (main.go:8) FUNCDATA $1, gclocals·cc5cad6002ade825472fe3ad2912087d(SB)
0x0021 00033 (main.go:8) FUNCDATA $3, gclocals·1e847dfe98917b7bcc7220b0d92d534f(SB)
0x0021 00033 (main.go:8) PCDATA $2, $0
0x0021 00033 (main.go:8) PCDATA $0, $0
0x0021 00033 (main.go:8) MOVQ $0, "".~r1 56(SP)
0x002a 00042 (main.go:8) PCDATA $2, $1
0x002a 00042 (main.go:8) LEAQ type.int(SB), AX
0x0031 00049 (main.go:8) PCDATA $2, $0
0x0031 00049 (main.go:8) MOVQ AX, (SP)
0x0035 00053 (main.go:8) CALL runtime.newobject(SB)
0x003a 00058 (main.go:8) PCDATA $2, $1
0x003a 00058 (main.go:8) MOVQ 8(SP), AX
0x003f 00063 (main.go:8) PCDATA $0, $1
0x003f 00063 (main.go:8) MOVQ AX, "".&i 24(SP)
0x0044 00068 (main.go:8) MOVQ "".i 48(SP), CX
0x0049 00073 (main.go:8) PCDATA $2, $0
0x0049 00073 (main.go:8) MOVQ CX, (AX)
0x004c 00076 (main.go:9) PCDATA $2, $1
0x004c 00076 (main.go:9) LEAQ type.noalg.struct { F uintptr; "".i *int }(SB), AX // 这个结构体就是闭包的类型
0x0053 00083 (main.go:9) PCDATA $2, $0
0x0053 00083 (main.go:9) MOVQ AX, (SP)
0x0057 00087 (main.go:9) CALL runtime.newobject(SB) //相当于 new
0x005c 00092 (main.go:9) PCDATA $2, $1
0x005c 00092 (main.go:9) MOVQ 8(SP), AX
0x0061 00097 (main.go:9) PCDATA $0, $2
0x0061 00097 (main.go:9) MOVQ AX, ""..autotmp_5 16(SP)
0x0066 00102 (main.go:9) LEAQ "".f.func1(SB), CX
汇编我了解的也不是很多,但是我们可以看到确实生成了一个新的对象
0x004c 00076 (main.go:9) LEAQ type.noalg.struct { F uintptr; "".i *int }(SB), AX // 这个结构体就是闭包的类型
并且new了一个对象
0x0057 00087 (main.go:9) CALL runtime.newobject(SB) //相当于 new
跟预估的情况差不多。
总结:
闭包的实现是通过将原本应该分配在方法内的局部变量放在堆上并返回其地址来做到的。而且返回的并不是单纯的函数,而是返回了一个结构体,通过结构体来记录变量的改变。
到此这篇关于“Golang 闭包实现原理”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!您可能感兴趣的文章:
Golang中闭包的实现原理
defer ,panic,recover
Golang并发:并发协程的优雅退出
Golang 闭包实现原理
golang中的mmap使用
golang匿名函数和闭包学习笔记
golang 切片 接口_Golang简单入门教程——函数进阶使用
GoLang的匿名函数与闭包
Golang注册Eureka的工具包goeureka发布
golang 切片 接口_Golang简单入门教程——函数进阶篇