Golang延迟调用:
defer特性:
1. 关键字 defer 用于注册延迟调用。
2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
3. 多个defer语句,按先进后出的方式执行。
4. defer语句中的变量,在defer声明时就决定了。
defer用途:
1. 关闭文件句柄
2. 锁资源释放
3. 数据库连接释放
go语言 defer
go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。
defer 是先进后出
这个很自然,后面的语句会依赖前面的资源,因此如果先前面的资源先释放了,后面的语句就没法执行了。
-
package main
-
-
import
"fmt"
-
-
func main() {
-
var whatever [
5]
struct{}
-
-
for i :=
range whatever {
-
defer fmt.Println(i)
-
}
-
}
输出结果:
-
4
-
3
-
2
-
1
-
0
defer 碰上闭包
-
package main
-
-
import
"fmt"
-
-
func main() {
-
var whatever [
5]
struct{}
-
for i :=
range whatever {
-
defer
func() { fmt.Println(i) }()
-
}
-
}
输出结果:
-
4
-
4
-
4
-
4
-
4
其实go说的很清楚,我们一起来看看go spec如何说的
我们先看一下官方对defer
的解释:
Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved a new but the actual function is not invoked.
Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the “defer” statement is executed.
翻译一下:
每次defer语句执行的时候,会把函数“压栈”,函数参数会被拷贝下来;当外层函数(非代码块,如一个for循环)退出时,defer函数按照定义的逆序执行;如果defer执行的函数为nil, 那么会在最终调用函数的产生panic.
defer语句并不会马上执行,而是会进入一个栈,函数return前,会按先后出的顺序执行。也说是说最先被定义的defer语句最后执行。先进后出的原因是后面定义的函数可能会依赖前面的资源,自然要先执行;否则,如果前面先执行,那后面函数的依赖就没有了。
在defer函数定义时,对外部变量的引用是有两种方式的,分别是作为函数参数和作为闭包引用。
- 作为函数参数,则在defer定义时就把值传递给defer,并被cache起来;
- 作为闭包引用的话,则会在defer函数真正调用时根据整个上下文确定当前的值。
defer后面的语句在执行的时候,函数调用的参数会被保存起来,也就是复制了一份。真正执行的时候,实际上用到的是这个复制的变量,因此如果此变量是一个“值”,那么就和定义的时候是一致的。如果此变量是一个“引用”,那么就可能和定义的时候不一致。
也就是说函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4.
defer f.Close
这个大家用的都很频繁,但是go语言编程举了一个可能一不小心会犯错的例子.
-
package main
-
-
import
"fmt"
-
-
type Test
struct {
-
name
string
-
}
-
-
func (t *Test) Close() {
-
fmt.Println(t.name,
" closed")
-
}
-
func main() {
-
ts := []Test{{
"a"}, {
"b"}, {
"c"}}
-
for _, t :=
range ts {
-
defer t.Close()
-
}
-
}
输出结果:
-
c closed
-
c closed
-
c closed
这个输出并不会像我们预计的输出c b a,而是输出c c c
可是按照前面的go spec中的说明,应该输出c b a才对啊.
那我们换一种方式来调用一下.
-
package main
-
-
import
"fmt"
-
-
type Test
struct {
-
name
string
-
}
-
-
func (t *Test) Close() {
-
fmt.Println(t.name,
" closed")
-
}
-
func Close(t Test) {
-
t.Close()
-
}
-
func main() {
-
ts := []Test{{
"a"}, {
"b"}, {
"c"}}
-
for _, t :=
range ts {
-
defer Close(t)
-
}
-
}
输出结果:
-
c closed
-
b closed
-
a closed
这个时候输出的就是c b a
当然,如果你不想多写一个函数,也很简单,可以像下面这样,同样会输出c b a
看似多此一举的声明
-
package main
-
-
import
"fmt"
-
-
type Test
struct {
-
name
string
-
}
-
-
func (t *Test) Close() {
-
fmt.Println(t.name,
" closed")
-
}
-
func main() {
-
ts := []Test{{
"a"}, {
"b"}, {
"c"}}
-
for _, t :=
range ts {
-
t2 := t
-
defer t2.Close()
-
}
-
}
输出结果:
-
c closed
-
b closed
-
a closed
通过以上例子,结合
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked.
这句话。可以得出下面的结论:
defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。但是并没有说struct这里的this指针如何处理,通过这个例子可以看出go语言并没有把这个明确写出来的this指针当作参数来看待。
多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。
-
package main
-
-
func test(x int) {
-
defer
println(
"a")
-
defer
println(
"b")
-
-
defer
func() {
-
println(
100 / x)
// div0 异常未被捕获,逐步往外传递,最终终止进程。
-
}()
-
-
defer
println(
"c")
-
}
-
-
func main() {
-
test(
0)
-
}
输出结果:
-
c
-
b
-
a
-
panic: runtime
error:
integer divide
by zero
*
延迟调用参数在注册时求值或复制,可用指针或闭包 "延迟" 读取。
-
package main
-
-
func test() {
-
x, y :=
10,
20
-
-
defer
func(i int) {
-
println(
"defer:", i, y)
// y 闭包引用
-
}(x)
// x 被复制
-
-
x +=
10
-
y +=
100
-
println(
"x =", x,
"y =", y)
-
}
-
-
func main() {
-
test()
-
}
输出结果:
-
x =
20 y =
120
-
defer:
10
120
*
滥用 defer 可能会导致性能问题,尤其是在一个 "大循环" 里。
-
package main
-
-
import (
-
"fmt"
-
"sync"
-
"time"
-
)
-
-
var lock sync.Mutex
-
-
func test() {
-
lock.Lock()
-
lock.Unlock()
-
}
-
-
func testdefer() {
-
lock.Lock()
-
defer lock.Unlock()
-
}
-
-
func main() {
-
func() {
-
t1 := time.Now()
-
-
for i :=
0; i <
10000; i++ {
-
test()
-
}
-
elapsed := time.Since(t1)
-
fmt.Println(
"test elapsed: ", elapsed)
-
}()
-
func() {
-
t1 := time.Now()
-
-
for i :=
0; i <
10000; i++ {
-
testdefer()
-
}
-
elapsed := time.Since(t1)
-
fmt.Println(
"testdefer elapsed: ", elapsed)
-
}()
-
-
}
输出结果:
-
test
elapsed: 223
.162µ
s
-
testdefer
elapsed: 781
.304µ
s
defer陷阱
defer 与 closure
-
package main
-
-
import (
-
"errors"
-
"fmt"
-
)
-
-
func foo(a, b int) (i int, err error) {
-
defer fmt.Printf(
"first defer err %v\n", err)
-
defer
func(err error) { fmt.Printf(
"second defer err %v\n", err) }(err)
-
defer
func() { fmt.Printf(
"third defer err %v\n", err) }()
-
if b ==
0 {
-
err = errors.New(
"divided by zero!")
-
return
-
}
-
-
i = a / b
-
return
-
}
-
-
func main() {
-
foo(
2,
0)
-
}
输出结果:
-
third
defer err divided by zero!
-
second
defer err <
nil>
-
first
defer err <
nil>
解释:如果 defer 后面跟的不是一个 closure 最后执行的时候我们得到的并不是最新的值。
defer 与 return
-
package main
-
-
import
"fmt"
-
-
func foo() (i int) {
-
-
i =
0
-
defer
func() {
-
fmt.Println(i)
-
}()
-
-
return
2
-
}
-
-
func main() {
-
foo()
-
}
输出结果:
2
解释:在有具名返回值的函数中(这里具名返回值为 i),执行 return 2 的时候实际上已经将 i 的值重新赋值为 2。所以defer closure 输出结果为 2 而不是 1。
defer nil 函数
-
package main
-
-
import (
-
"fmt"
-
)
-
-
func test() {
-
var run
func() = nil
-
defer
run
()
-
fmt.
Println
("runs")
-
}
-
-
func
main
() {
-
defer
func() {
-
if err :=
recover(); err !=
nil {
-
fmt.Println(err)
-
}
-
}()
-
test()
-
}
输出结果:
-
runs
-
runtime
error: invalid memory address
or nil pointer dereference
解释:名为 test 的函数一直运行至结束,然后 defer 函数会被执行且会因为值为 nil 而产生 panic 异常。然而值得注意的是,run() 的声明是没有问题,因为在test函数运行完成后它才会被调用。
在错误的位置使用 defer
当 http.Get 失败时会抛出异常。
-
package main
-
-
import
"net/http"
-
-
func do() error {
-
res, err := http.Get(
"http://www.google.com")
-
defer res.Body.Close()
-
if err !=
nil {
-
return err
-
}
-
-
// ..code...
-
-
return
nil
-
}
-
-
func main() {
-
do()
-
}
输出结果:
panic: runtime error: invalid memory address or nil pointer dereference
因为在这里我们并没有检查我们的请求是否成功执行,当它失败的时候,我们访问了 Body 中的空变量 res ,因此会抛出异常
解决方案
总是在一次成功的资源分配下面使用 defer ,对于这种情况来说意味着:当且仅当 http.Get 成功执行时才使用 defer
-
package main
-
-
import
"net/http"
-
-
func do() error {
-
res, err := http.Get(
"http://xxxxxxxxxx")
-
if res !=
nil {
-
defer res.Body.Close()
-
}
-
-
if err !=
nil {
-
return err
-
}
-
-
// ..code...
-
-
return
nil
-
}
-
-
func main() {
-
do()
-
}
在上述的代码中,当有错误的时候,err 会被返回,否则当整个函数返回的时候,会关闭 res.Body 。
解释:在这里,你同样需要检查 res 的值是否为 nil ,这是 http.Get 中的一个警告。通常情况下,出错的时候,返回的内容应为空并且错误会被返回,可当你获得的是一个重定向 error 时, res 的值并不会为 nil ,但其又会将错误返回。上面的代码保证了无论如何 Body 都会被关闭,如果你没有打算使用其中的数据,那么你还需要丢弃已经接收的数据。
不检查错误
在这里,f.Close() 可能会返回一个错误,可这个错误会被我们忽略掉
-
package main
-
-
import
"os"
-
-
func do() error {
-
f, err := os.Open(
"book.txt")
-
if err !=
nil {
-
return err
-
}
-
-
if f !=
nil {
-
defer f.Close()
-
}
-
-
// ..code...
-
-
return
nil
-
}
-
-
func main() {
-
do()
-
}
改进一下
-
package main
-
-
import
"os"
-
-
func do() error {
-
f, err := os.Open(
"book.txt")
-
if err !=
nil {
-
return err
-
}
-
-
if f !=
nil {
-
defer
func() {
-
if err := f.Close(); err !=
nil {
-
// log etc
-
}
-
}()
-
}
-
-
// ..code...
-
-
return
nil
-
}
-
-
func main() {
-
do()
-
}
再改进一下
通过命名的返回变量来返回 defer 内的错误。
-
package main
-
-
import
"os"
-
-
func do() (err error) {
-
f, err := os.Open(
"book.txt")
-
if err !=
nil {
-
return err
-
}
-
-
if f !=
nil {
-
defer
func() {
-
if ferr := f.Close(); ferr !=
nil {
-
err = ferr
-
}
-
}()
-
}
-
-
// ..code...
-
-
return
nil
-
}
-
-
func main() {
-
do()
-
}
释放相同的资源
如果你尝试使用相同的变量释放不同的资源,那么这个操作可能无法正常执行。
-
package main
-
-
import (
-
"fmt"
-
"os"
-
)
-
-
func do() error {
-
f, err := os.Open(
"book.txt")
-
if err !=
nil {
-
return err
-
}
-
if f !=
nil {
-
defer
func() {
-
if err := f.Close(); err !=
nil {
-
fmt.Printf(
"defer close book.txt err %v\n", err)
-
}
-
}()
-
}
-
-
// ..code...
-
-
f, err = os.Open(
"another-book.txt")
-
if err !=
nil {
-
return err
-
}
-
if f !=
nil {
-
defer
func() {
-
if err := f.Close(); err !=
nil {
-
fmt.Printf(
"defer close another-book.txt err %v\n", err)
-
}
-
}()
-
}
-
-
return
nil
-
}
-
-
func main() {
-
do()
-
}
输出结果: defer close book.txt err close ./another-book.txt: file already closed
当延迟函数执行时,只有最后一个变量会被用到,因此,f 变量 会成为最后那个资源 (another-book.txt)。而且两个 defer 都会将这个资源作为最后的资源来关闭
解决方案:
-
package main
-
-
import (
-
"fmt"
-
"io"
-
"os"
-
)
-
-
func do() error {
-
f, err := os.Open(
"book.txt")
-
if err !=
nil {
-
return err
-
}
-
if f !=
nil {
-
defer
func(f io.Closer) {
-
if err := f.Close(); err !=
nil {
-
fmt.Printf(
"defer close book.txt err %v\n", err)
-
}
-
}(f)
-
}
-
-
// ..code...
-
-
f, err = os.Open(
"another-book.txt")
-
if err !=
nil {
-
return err
-
}
-
if f !=
nil {
-
defer
func(f io.Closer) {
-
if err := f.Close(); err !=
nil {
-
fmt.Printf(
"defer close another-book.txt err %v\n", err)
-
}
-
}(f)
-
}
-
-
return
nil
-
}
-
-
func main() {
-
do()
-
}
异常处理
Golang 没有结构化异常,使用 panic 抛出错误,recover 捕获错误。
异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。
panic:
- 内置函数
- 假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
- 返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
- 直到goroutine整个退出,并报告错误
recover:
1、内置函数
2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
3、一般的调用建议
a). 在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行
b). 可以获取通过panic传递的error
注意:
- 利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。
- recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
- 多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。
-
package main
-
-
func main() {
-
test()
-
}
-
-
func test() {
-
defer
func() {
-
if err :=
recover(); err !=
nil {
-
println(err.(
string))
// 将 interface{} 转型为具体类型。
-
}
-
}()
-
-
panic(
"panic error!")
-
}
输出结果:
panic error!
由于 panic、recover 参数类型为 interface{},因此可抛出任何类型对象。
-
func panic(v interface{})
-
func recover() interface{}
向已关闭的通道发送数据会引发panic
-
package main
-
-
import (
-
"fmt"
-
)
-
-
func main() {
-
defer
func() {
-
if err :=
recover(); err !=
nil {
-
fmt.Println(err)
-
}
-
}()
-
-
var ch
chan
int =
make(
chan
int,
10)
-
close(ch)
-
ch <-
1
-
}
输出结果:
send on closed channel
延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。
-
package main
-
-
import
"fmt"
-
-
func test() {
-
defer
func() {
-
fmt.Println(
recover())
-
}()
-
-
defer
func() {
-
panic(
"defer panic")
-
}()
-
-
panic(
"test panic")
-
}
-
-
func main() {
-
test()
-
}
输出:
defer panic
捕获函数 recover 只有在延迟调用内直接调用才会终止错误,否则总是返回 nil。任何未捕获的错误都会沿调用堆栈向外传递。
-
package main
-
-
import
"fmt"
-
-
func test() {
-
defer
func() {
-
fmt.Println(
recover())
//有效
-
}()
-
defer
recover()
//无效!
-
defer fmt.Println(
recover())
//无效!
-
defer
func() {
-
func() {
-
println(
"defer inner")
-
recover()
//无效!
-
}()
-
}()
-
-
panic(
"test panic")
-
}
-
-
func main() {
-
test()
-
}
输出:
-
defer inner
-
<
nil>
-
test
panic
使用延迟匿名函数或下面这样都是有效的。
-
package main
-
-
import (
-
"fmt"
-
)
-
-
func except() {
-
fmt.Println(
recover())
-
}
-
-
func test() {
-
defer except()
-
panic(
"test panic")
-
}
-
-
func main() {
-
test()
-
}
输出结果:
test panic
如果需要保护代码 段,可将代码块重构成匿名函数,如此可确保后续代码被执 。
-
package main
-
-
import
"fmt"
-
-
func test(x, y int) {
-
var z
int
-
-
func() {
-
defer
func() {
-
if
recover() !=
nil {
-
z =
0
-
}
-
}()
-
panic(
"test panic")
-
z = x / y
-
return
-
}()
-
-
fmt.Printf(
"x / y = %d\n", z)
-
}
-
-
func main() {
-
test(
2,
1)
-
}
输出结果:
x / y = 0
除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态。
-
type
error interface {
-
Error()
string
-
}
标准库 errors.New 和 fmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。
-
package main
-
-
import (
-
"errors"
-
"fmt"
-
)
-
-
var ErrDivByZero = errors.New(
"division by zero")
-
-
func div(x, y int) (int, error) {
-
if y ==
0 {
-
return
0, ErrDivByZero
-
}
-
return x / y,
nil
-
}
-
-
func main() {
-
defer
func() {
-
fmt.Println(
recover())
-
}()
-
switch z, err := div(
10,
0); err {
-
case
nil:
-
println(z)
-
case ErrDivByZero:
-
panic(err)
-
}
-
}
输出结果:
division by zero
Go实现类似 try catch 的异常处理
-
package main
-
-
import
"fmt"
-
-
func Try(fun func(), handler func(interface{})) {
-
defer
func() {
-
if err :=
recover(); err !=
nil {
-
handler(err)
-
}
-
}()
-
fun()
-
}
-
-
func main() {
-
Try(
func() {
-
panic(
"test panic")
-
},
func(err interface{}) {
-
fmt.Println(err)
-
})
-
}
输出结果:
test panic
如何区别使用 panic 和 error 两种方式?
惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error。
转载:https://blog.csdn.net/fly910905/article/details/104464397