GO资源管理和错误处理
资源管理 defer
程序中的资源管理通常是指一些成对出现的操作,例如:文件的打开和关闭,数据库的连接和释放,这通常不是一件复杂的事情,但是程序可能出错从中间跳出来,导致资源得不到释放,最终会严重地影响系统的整体性能。GO语言是通过defer调用来实现资源管理的。Go垃圾回收(gc)是内存垃圾回收,分配给对象的内存回收,对于资源必须手动释放还给操作系统。
- 确保调用在函数结束的时候发生
- defer内部相当于一个栈,defer列表为先进后出
- 参数在defer语句中是进行计算的,只是没有马上调用
- 使用defer调用的场景:
Open / Close
Lock / Unlock
PrintHeader / PrintFooter
例子:defer在函数结束的时候发生
package main
import "fmt"
func main(){
defer fmt.Println(1) //放在栈中函数结束的时候再执行
fmt.Println(2)
}
//输出结果:2 1
例子:defer列表先进后出
package main
import "fmt"
func main(){
//defer 在函数结束的时候执行 包括函数主动return或程序出错了
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
//return
panic("好像出错了...")
fmt.Println(4)
}
//输出结果:3 2 1 panic: 好像出错了...
例子:参数在defer语句中计算
package main
import "fmt"
func main(){
for i:=0 ; i<6 ; i++{
defer fmt.Println(i)
}
}
//输出结果:5 4 3 2 1 0 不是一直都是 5
文件资源管理
package main
import (
"bufio"
"ccmouse/defer/fibonacci"
"fmt"
"os"
)
func writeFile(){
//创建一个文件 ---关闭文件资源
file,err := os.Create("fibonacci.txt")
if err != nil {
panic(err)
}
//关闭文件资源
defer file.Close()
f := fibonacci.Fibonacci()
//使用缓存写入内存中比较快----最后写入文件中
witer := bufio.NewWriter(file)
for i:=1;i<21 ;i++ {
fmt.Fprintln(witer,f())
}
//最后写入文件中
defer witer.Flush()
}
func main(){
writeFile()
}
package fibonacci
func Fibonacci() func()int{
a,b := 0,1
return func()int{
a,b = b,a+b
return a
}
}
错误处理
GO语言的处理通常了 panic、defer、recover结合使用的。GO中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。
panic
- 内建函数
- 假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
- 返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行,这里的defer 有点类似 try-catch-finally 中的 finally
- 直到goroutine整个退出,并报告错误
- 如果没有遇见 recover, 程序退出
file,err := os.OpenFile("fibonacci.txt",os.O_EXCL|os.O_CREATE,0666)
//自己创建一个错误
err1 := errors.New("this is custom error")
fmt.Println(err1)
//If there is an error, it will be of type *PathError.
if err != nil {
if pathErr,ok := err.(*os.PathError);!ok{ //err接口类型的断言
//抛出异常
panic(err)
}else {
//Op 操作 Path路径 Err错误信息
log.Println(pathErr.Op,pathErr.Path,pathErr.Err)
}
fmt.Println("errror:",err.Error())
return
//panic(err)
}
recover
- 内建函数
- 仅在 defer 调用中使用,如果无法处理,可重新 Panic
- 用来控制一个goRoutine的panicking行为,捕获panic,从而影响应用的行为
- 调用建议:在defer函数中,通过recover来终止一个goRoutine的panicking过程,从而恢复正常代码的执行;可以获取通过panic传递的error
简单来讲:go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。
package main
import "fmt"
func myFuc(){
fmt.Println("c")
panic("this is custom error")
fmt.Println("d")
}
func main(){
fmt.Println("a")
//先定义defer 不然捕获不到panic
defer func(){
if err := recover() ; err != nil { //recover捕获错误信息 并且处理
fmt.Println(err)
}
}()
myFuc()
fmt.Println("b")
}
//输出信息:a c this is custom error
统一得错误处理逻辑
- 业务处理逻辑封装到函数中,遇见错误就return,返回错误error
- 封装一个错误处理的函数,分级别处理错误
package main
import (
"ccmouse/errhandle/handle"
"github.com/astaxie/beego/logs"
"net/http"
"os"
)
//处理业务逻辑的函数类型
type AppHandler func(w http.ResponseWriter,r *http.Request) error
//处理返回错误的函数
//函数式编程输入是函数输出是函数
func ErrWrapper(ah AppHandler) func(w http.ResponseWriter,r *http.Request){
//返回的是Http.HandleFunc 需要的函数参数
return func(w http.ResponseWriter, r *http.Request) {
//ah处理的是逻辑业务
err := ah(w,r)
//-----以下是处理错误的程序-------
if err != nil { //很多不同的错误 需要区别处理
code := http.StatusOK
//记录错误日志
logs.Warning("handling request err: %s\n",err.Error())
switch {
//文件不存在的错误
case os.IsNotExist(err):
code = http.StatusNotFound
//文件没有权限
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(w,http.StatusText(code),code)
}
}
}
func main(){
http.HandleFunc("/list/", ErrWrapper(handle.FileList))
http.ListenAndServe(":8888",nil)
}
package handle
import (
"io/ioutil"
"net/http"
"os"
)
func FileList(w http.ResponseWriter,r *http.Request) error{
path := r.URL.Path[len("/list/"):]
file,err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
all,err := ioutil.ReadAll(file)
if err != nil {
return err
}
w.Write(all)
return nil
}
- 处理用户自定义的错误类型
package main
import (
"ccmouse/errhandle/handle"
"github.com/astaxie/beego/logs"
"github.com/gpmgo/gopm/modules/log"
"net/http"
"os"
)
type AppHandler func(w http.ResponseWriter,r *http.Request) error
//区分能给用户看的错误信息和不能给用户看的错误信息
//定义可以给用户看到的错误类型
type UserError interface {
error
Message() string
}
//处理返回错误的函数
func ErrWrapper(ah AppHandler) func(w http.ResponseWriter,r *http.Request){
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
err1 :=recover()
if err1 != nil {
log.Warn("sever err: %v\n",err1)
http.Error(w,http.StatusText(http.StatusInternalServerError),http.StatusInternalServerError)
}
}()
err := ah(w,r)
if err != nil { //很多不同的错误 需要区别处理
//用户可以看见的错误信息单独处理
if userErr,ok := err.(UserError);ok{
http.Error(w,userErr.Error(),http.StatusBadRequest)
return
}
//以下是系统自带的错误信息
code := http.StatusOK
//记录错误日志
logs.Warning("handling request err: %s\n",err.Error())
switch {
//文件不存在的错误
case os.IsNotExist(err):
code = http.StatusNotFound
//文件没有权限
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(w,http.StatusText(code),code)
}
}
}
func main(){
http.HandleFunc("/", ErrWrapper(handle.FileList))
http.ListenAndServe(":8888",nil)
}
package handle
import (
"io/ioutil"
"net/http"
"os"
"strings"
)
//定义用户能看见的error
type UserError string
func (e UserError) Error() string{
return e.Message()
}
func (e UserError)Message() string{
return string(e)
}
const PREFIX = "/list/"
func FileList(w http.ResponseWriter,r *http.Request) error{
//判断path里面有没有/list/
index := strings.Index(r.URL.Path,PREFIX)
if index < 0 {
//自定义用户信息
return UserError("path must start with '/list/'")
}
path := r.URL.Path[len(PREFIX):]
file,err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
all,err := ioutil.ReadAll(file)
if err != nil {
return err
}
w.Write(all)
return nil
}