golang 系列 (四) defer、error、panic, go语句

本文深入探讨了Go语言中的高级特性,包括defer语句的使用、error和panic的处理机制、以及go语句和通道类型在并发编程中的应用。通过实例讲解了defer语句的执行顺序、error和panic的区别与联系、以及如何使用go语句实现并发。

defer语句

defer 语句仅能被放置在函数或方法中。defer 不能调用内置函数以及标准库代码包 unsafe 的函数.

// 读取文件, 转换为 byte 数组
func readFile(path string) ([]byte, error) {
	file,err := os.Open(path)
	if err != nil {
		return nil,err
	}
	// 定义延迟语句, 无论本函数正常返回还是发生了异常其中的file.Close()都会在本函数即将退出那一刻被执行。
	defer file.Close()
	return ioutil.ReadAll(file)
}

多个 defer 语句

函数出现多个defer语句时,会按出现顺序的倒序执行。

func deferPrint() {
    defer func(){
        fmt.Print(1)
    }()
    defer func() {
        fmt.Print(2)
    }()
    defer func() {
        fmt.Print(3)
    }()
    fmt.Print(4)
}

得到的结果是: 4321

如果 defer 调用的函数有参数传递的话, 要注意在程序执行到该语句时就已经传递进去并执行了.

func deferPrint2() {
	// 定义函数对象
	f := func(i int) int {
		fmt.Printf("%d ", i)
		return i * 10
	}
	for i := 1; i < 5; i++ {
		// 调用函数对象时传递到函数中的参数会被直接取值并且函数对象会直接执行. 但 defer 调用的语句却会按照压栈的顺序先进后出. 
		defer fmt.Printf("%d ", f(i))
	}
}

得到的结果是: 1 2 3 4 40 30 20 10

如果 defer 调用匿名函数时引用了外部变量的话, 结果会在函数调用完毕最后一刻统一取该外部变量的值. 如下函数因为defer 语句在最后执行时, for循环已经全部执行完了, 此时变量 i = 5 , 所以最终打印出来的结果就都是 5 .

func deferPrint3() {
	for i := 1; i < 5; i++ {
		// defer 调用匿名函数时引用了外部变量
		defer func() {
			fmt.Printf("%d ", i)
		}()
	}
}

得到的结果是: 5 5 5 5

若想得到理想的结果应该把变量作为参数传递到匿名函数中. 如下:

func deferPrint4() {
	for i := 1; i < 5; i++ {
		defer func(n int) {
			fmt.Printf("%d ", n)
		}(i)
	}
}

得到的结果是: 4 3 2 1

error 语句

error是Go语言内置的一个接口类型. 如果一个类中包含 Error() string 这个函数就相当于实现了 error 接口.

产生并判断 error 的方式如下:

// 读取文件, 转换为 byte 数组
func readFile(path string) ([]byte, error) {	
	// 如果 path 为空的话返回异常.
	if path == ""{
		// 创建异常对象返回
		return nil, errors.New("The path is empty!")
	}
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	// 定义延迟语句, 无论本函数正常返回还是发生了异常其中的file.Close()都会在本函数即将退出那一刻被执行。
	defer file.Close()
	return ioutil.ReadAll(file)
}

error 的类型

当产生某种异常时, 我们可以使用 Golang 定义的错误类型来判断该错误属于哪种类型. 比如:

// 代表读取方已无更多数据可读
if err == io.EOF{
	// 关闭流	
	file.Close()
}

panic 语句

关于 panic 这里有一篇文章可以参考下: https://www.jianshu.com/p/f30da01eea97

理解:
error(错误) 指的是可预见的错误. 例如在只接收日期格式的输入函数中接收到其他格式.
panic(异常) 指的是在不可能出现的地方出现错误. 例如使用日期格式接收了其他格式的输入.

总结:
当错误出现时如果没有处理或者处理不当时就会演变成异常.

使用:

func main() {
    fmt.Println("Starting the program")
    panic("A severe error occurred: stopping the program!")
    fmt.Println("Ending the program")
}

打印结果:

Starting the program
panic: A severe error occurred: stopping the program!

goroutine 1 [running]:
main.main()

panic 函数可接受一个 interface{} 类型的值作为其参数,interface{} 代表空接口。Go中的任何类型都是它的实现类型。(类似于 java 中的 Object 概念) 即我们可以传任何类型的参数给 panic。如果不处理异常, 会造成程序崩溃的后果.

recover 语句

处理 panic 可以使用内部函数 recover

使用:

// 定义x, y变量
var x, y int

func main() {
	fmt.Println("启动程序")
	initNum()
	step1()
	fmt.Printf("结束程序. x = %d, y = %d", x, y)
}

// 初始化变量值
func initNum()  {
	x = 1
	y = 2
}

func step1() {
	// 定义处理异常的defer函数, 要注意defer recover函数必须定义在panic之前
	defer func() {
		if e := recover(); e != nil {
			fmt.Printf("出现异常: %s\n", e)
			// 恢复数据
			initNum()
		}
	}()
	// 调换x, y的值
	x ,y = 2, 1
	// 使用panic创建异常
	if x == 2{
		panic("x == 2")
	}
}	

打印结果:

启动程序
出现异常: x == 2
结束程序. x = 1, y = 2

recover 函数会返回一个 interface{} 类型的值,如果 e 不为 nil 那么就说明有异常产生。这时要根据情况做相应处理。一旦defer语句中的 recover 函数被调用,异常就会被恢复.

go 语句

go语句和通道类型是Go语言的并发编程理念的最终体现。首先, go 语句是异步并且不即时执行的. 例如:

func main() {
    go fmt.Println("Go!")
}

这个程序不会打印结果. 因为在 go 语句异步执行之前 main 函数已经执行完毕并且退出程序了. 由于 go 语句执行时间的不确定性, 所以 Golang 中提供了几种方式来协调运行.

方式1 : time.Sleep

func main() {
    go fmt.Println("Go!")
	// 主线程延迟结束100毫秒
    time.Sleep(100 * time.Millisecond)
}

打印结果 : Go!

方式2 : runtime.Gosched

func main() {
    go fmt.Println("Go!!")
    runtime.Gosched()
}

打印结果 : Go!!

runtime.Gosched 函数的作用是让当前正在运行的 Goroutine (这里是运行 main 函数的那个Goroutine)暂停,而让系统转去运行其他的 Goroutine (对应 go fmt.Println("Go!!") 的那个Goroutine)。

方式3 : sync.WaitGroup

func main() {
	var wg sync.WaitGroup
	wg.Add(3)
	go func () {
		fmt.Println("Go!1")
		wg.Done()
	}()

	go func() {
		fmt.Println("Go!2")
		wg.Done()
	}()

	go func() {
		fmt.Println("Go!3")
		wg.Done()
	}()

	wg.Wait()
}

打印结果:

Go!3
Go!1
Go!2

sync.WaitGroup 有三个方法, Add()Done()Wait()
sync.WaitGroup 内部有个值代表异步任务的数量, 初始化为 0 , 每次调用 Add 函数会增加指定的数量。
Done 会使任务数量减 1 。
wait 方法会使当前 Goroutine 阻塞直到任务数量为 0。
上例中我们在 main 函数启用了三个 Goroutine 来封装三个Go函数。每个匿名函数的最后都调用了wg.Done 方法。当这三个 go 函数都调用过 wg.Done 函数之后,处于main函数最后的那条 wg.Wait() 语句就不会阻塞,程序执行结束。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值