defer는 golang에서 제공하는 키워드로 함수나 메소드가 실행을 완료하고 반환한 후에 호출됩니다.
각 연기는 연기 함수를 스택에 푸시합니다. 함수나 메서드가 호출되면 실행을 위해 스택에서 꺼내집니다. 따라서 여러 연기의 실행 순서는 먼저 들어와 마지막으로 나옵니다.
for i := 0; i <= 3; i++ { defer fmt.Print(i) } //输出结果时 3,2,1,0
defer
의 트리거 타이밍은 공식 웹사이트에서 매우 명확하게 설명합니다.
"defer" 문은 주변 함수가 return 문을 실행했기 때문에 주변 함수가 반환되는 순간까지 실행이 연기되는 함수를 호출합니다. , 함수 본문의 끝에 도달했거나 해당 고루틴이 패닉 상태이기 때문입니다.
- defer 문에 싸인 함수가 반환될 때
- defer 문에 싸인 함수가 끝까지 실행될 때
현재 고루틴 패닉
//输出结果:return前执行defer func f1() { defer fmt.Println("return前执行defer") return } //输出结果:函数执行 // 函数执行到最后 func f2() { defer fmt.Println("函数执行到最后") fmt.Println("函数执行") } //输出结果:panic前 第一个defer在Panic发生时执行,第二个defer在Panic之后声明,不能执行到 func f3() { defer fmt.Println("panic前") panic("panic中") defer fmt.Println("panic后") }
defer, return, return value 실행 순서
먼저 3가지 예를 살펴보겠습니다
func f1() int { //匿名返回值 var r int = 6 defer func() { r *= 7 }() return r } func f2() (r int) { //有名返回值 defer func() { r *= 7 }() return 6 } func f3() (r int) { //有名返回值 defer func(r int) { r *= 7 }(r) return 6 }
f1의 실행 결과는 6, f2의 실행 결과는 42, f3의 실행 결과는 6
golang 공식 문서에 소개됨 return, defer 및 return value의 실행 순서:
주변 함수가 명시적인 return 문을 통해 반환하는 경우 deferred 함수는 해당 return 문에 의해 결과 매개 변수가 설정된 후 함수가 실행되기 전에 실행됩니다.
1. 먼저 반환 값에 값을 할당합니다
2. defer 문을 실행합니다
3. 함수 반환 반환
f1의 결과는 6입니다. f1은 익명 반환 값입니다. 익명 반환 값은 return이 실행될 때 선언되므로 defer가 선언되면 익명 반환 값에 접근할 수 없습니다.
f2는 먼저 반환 값 r, r=6을 할당하고 defer 문을 실행하고 defer를 r, r = 42로 수정한 다음 함수가 반환됩니다.
f3은 명명된 반환 값이지만 r이 defer의 매개 변수로 사용되기 때문에 defer를 선언할 때 매개 변수가 복사되어 전달되므로 defer는 defer 함수의 로컬 매개 변수에만 영향을 미치고 defer의 반환에는 영향을 주지 않습니다. 함수를 호출합니다.
클로저 및 익명 함수
익명 함수: 함수 이름이 없는 함수입니다.
클로저: 다른 함수 범위 내에서 변수를 사용할 수 있는 함수입니다.
for i := 0; i <= 3; i++ { defer func() { fmt.Print(i) } } //输出结果时 3,3,3,3 因为defer函数的i是对for循环i的引用,defer延迟执行,for循环到最后i是3,到defer执行时i就 是3 for i := 0; i <= 3; i++ { defer func(i int) { fmt.Print(i) }(i) } //输出结果时 3,2,1,0 因为defer函数的i是在defer声明的时候,就当作defer参数传递到defer函数中
defer 소스 코드 분석
defer 구현 소스 코드는 Runtime.deferproc에 있습니다
그런 다음 함수가 반환되기 전에 Runtime.deferreturn 함수를 실행하세요.
먼저 defer 구조를 이해하세요.
type _defer struct { siz int32 started bool sp uintptr // sp at time of defer pc uintptr fn *funcval _panic *_panic // panic that is running defer link *_defer }
sp와 pc는 각각 호출자의 스택 포인터와 프로그램 카운터를 가리키고, fn은 defer 키워드에 전달된 함수이고 Panic은 defer가 실행되도록 하는 Panic입니다.
defer 키워드가 발견될 때마다 defer 함수는 런타임으로 변환됩니다.deferproc
deferproc는 newdefer를 통해 지연 함수를 생성하고 이 새로운 지연 함수를 현재 고루틴의 _defer 연결 목록에 정지시킵니다.
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn sp := getcallersp() argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) callerpc := getcallerpc() d := newdefer(siz) if d._panic != nil { throw("deferproc: d.panic != nil after newdefer") } d.fn = fn d.pc = callerpc d.sp = sp switch siz { case 0: // Do nothing. case sys.PtrSize: *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp)) default: memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz)) } return0() }
newdefer는 먼저 sched의 _defer 구조 및 현재 p의 deferpool deferpool에 _defer가 없으면 새 _defer를 초기화합니다.
_defer는 현재 g와 연관되어 있으므로 defer는 현재 g에 대해서만 유효합니다.
d.link = gp._defer
gp._defer = d //연결된 목록을 사용하여 현재 g의 모든 defer를 연결
func newdefer(siz int32) *_defer { var d *_defer sc := deferclass(uintptr(siz)) gp := getg() if sc < uintptr(len(p{}.deferpool)) { pp := gp.m.p.ptr() if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil { ..... d := sched.deferpool[sc] sched.deferpool[sc] = d.link d.link = nil pp.deferpool[sc] = append(pp.deferpool[sc], d) } if n := len(pp.deferpool[sc]); n > 0 { d = pp.deferpool[sc][n-1] pp.deferpool[sc][n-1] = nil pp.deferpool[sc] = pp.deferpool[sc][:n-1] } } ...... d.siz = siz d.link = gp._defer gp._defer = d return d }
deferreturn 현재 g에서 _defer 연결 목록을 꺼내어 실행합니다. 각 _defer는 freedefer를 호출합니다. _defer 구조를 해제하고 _defer 구조를 현재 p의 deferpool에 넣습니다.
defer 성능 분석
defer는 개발 중 리소스 릴리스, 패닉 캡처 등에 매우 유용합니다. 일부 개발자는 프로그램 성능에 대한 연기 및 프로그램의 남용 연기에 대한 영향을 고려하지 않았을 가능성이 있습니다.
성능 테스트에서는 연기가 여전히 성능에 어느 정도 영향을 미치는 것으로 나타났습니다. Yuchen의 Go 성능 최적화 팁 4/1에는 defer 문으로 인해 발생하는 추가 오버헤드에 대한 몇 가지 테스트가 있습니다.
테스트 코드
var mu sync.Mutex func noDeferLock() { mu.Lock() mu.Unlock() } func deferLock() { mu.Lock() defer mu.Unlock() } func BenchmarkNoDefer(b *testing.B) { for i := 0; i < b.N; i++ { noDeferLock() } } func BenchmarkDefer(b *testing.B) { for i := 0; i < b.N; i++ { deferLock() }
테스트 결과:
BenchmarkNoDefer-4 100000000 11.1 ns/op BenchmarkDefer-4 36367237 33.1 ns/op
이전 소스 코드 분석을 통해 defer는 먼저 deferproc을 호출하여 매개 변수를 복사하고 deferreturn도 관련 정보를 추출하여 실행을 지연한다는 것을 알 수 있습니다. 명세서를 직접 호출하는 것이 더 비쌉니다.
defer의 성능은 높지 않습니다. 각 defer가 한 func에서 여러 번 발생하면 성능 소모는 20ns*n이 됩니다.
해결 방법: 예외 캡처가 필요한 경우를 제외하고 다른 자원 재활용 연기에는 defer를 사용해야 합니다. 실패를 판단한 후 goto를 사용하여 자원 재활용 코드 영역으로 이동할 수 있습니다. 경쟁적인 자원의 경우, 사용 후 즉시 자원을 해제할 수 있으므로 경쟁적인 자원을 최적으로 사용할 수 있습니다.