defer ialah kata kunci yang disediakan oleh golang, yang dipanggil selepas fungsi atau kaedah melengkapkan pelaksanaan dan kembali.
Setiap penangguhan akan menolak fungsi penangguhan ke dalam tindanan Apabila fungsi atau kaedah dipanggil, ia akan dikeluarkan daripada tindanan untuk pelaksanaan Oleh itu, urutan pelaksanaan berbilang penangguhan adalah yang pertama masuk, yang terakhir.
for i := 0; i <= 3; i++ { defer fmt.Print(i) } //输出结果时 3,2,1,0
Masa pencetus penangguhan
Tapak web rasmi menyatakannya dengan jelas:
Pernyataan "menangguhkan" memanggil fungsi yang pelaksanaannya ditangguhkan kepada pada saat fungsi sekeliling kembali, sama ada kerana fungsi sekeliling melaksanakan pernyataan pulangan, mencapai penghujung badan fungsinya atau kerana goroutine yang sepadan sedang panik.
- Apabila fungsi dibalut dalam pernyataan tangguh kembali
- Apabila fungsi yang dibalut dalam penyata tangguh dilaksanakan hingga akhir
Apabila panik goroutine semasa
//输出结果: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后") }
Salin selepas log masuk
menangguhkan, kembali, kembali Susunan pelaksanaan nilai
Mari kita lihat 3 contoh dahulu
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 }
Hasil pelaksanaan f1 ialah 6, hasil pelaksanaan f2 ialah 42, dan hasil pelaksanaan f3 ialah 6
Dokumentasi rasmi Golang memperkenalkan susunan pelaksanaan nilai pemulangan, tangguh dan pemulangan:
jika fungsi sekeliling kembali melalui penyataan pemulangan eksplisit, fungsi tertunda akan dilaksanakan selepas sebarang keputusan parameter ditetapkan oleh pernyataan pulangan itu tetapi sebelum fungsi kembali kepada pemanggilnya.
1 Tetapkan nilai pulangan dahulu
2
3. Balut pengembalian fungsi
f2 mula-mula memberikan nilai pulangan r, r=6, melaksanakan pernyataan tangguh, tangguh mengubah suai r, r = 42, dan kemudian fungsi kembali.
f3 ialah nilai pulangan yang dinamakan, tetapi kerana r digunakan sebagai parameter penangguhan, apabila mengisytiharkan penangguhan, parameter disalin dan diluluskan, jadi penangguhan hanya akan menjejaskan parameter tempatan fungsi penangguhan dan tidak akan menjejaskan panggilan fungsi.
Penutupan dan fungsi tanpa nama Fungsi tanpa nama: fungsi tanpa nama fungsi.
Penutupan: Fungsi yang boleh menggunakan pembolehubah dalam skop fungsi lain.
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函数中
tunda analisis kod sumberKod sumber pelaksanaan tangguh berada dalam runtime.deferproc
dan kemudian jalankan fungsi runtime.deferreturn sebelum fungsi kembali.
Mula-mula fahami struktur penangguhan:
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 }
Setiap kali kata kunci tangguh ditemui, fungsi tangguh akan ditukar menjadi masa jalan.deferproc
deferproc mencipta fungsi kelewatan melalui newdefer dan menggantung fungsi tunda baharu ini pada senarai terpaut _defer goroutine semasa
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() }
_defer dikaitkan dengan g semasa, jadi penangguhan hanya sah untuk g semasa.
d.link = gp._defer
gp._defer = d //Gunakan senarai terpaut untuk menyambungkan semua penangguhan g semasa
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 }
menangguhkan analisis prestasimenangguhkan sangat berguna untuk pelepasan sumber, penangkapan panik, dsb. semasa pembangunan. Ada kemungkinan bahawa sesetengah pembangun tidak mempertimbangkan kesan penangguhan terhadap prestasi program dan penangguhan penyalahgunaan dalam program mereka.
Dalam ujian prestasi, boleh didapati bahawa penangguhan masih mempunyai sedikit kesan ke atas prestasi. Petua pengoptimuman prestasi Yuchen's Go 4/1, terdapat beberapa ujian pada overhed tambahan yang disebabkan oleh penyata penangguhan.
Kod ujian
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() }
Hasil ujian:
BenchmarkNoDefer-4 100000000 11.1 ns/op BenchmarkDefer-4 36367237 33.1 ns/op