009 Go-更多流程控制(defer error panic)

http://note.youdao.com/noteshare?id=b19aee1827007826bff67e08b6dce5e2&sub=DCFC8BF77D5F46CFA2952181534D2B7B

 

009 Go-更多流程控制(defer error panic)

一、defer

1.1 示例--文件操作-确保文件流关闭

1.2 示例:函数中多次 defer——其执行顺序为其出现顺序的倒序。

二、Go语言异常处理——error

2.1 错误传递

2.2错误创造

三、异常处理——panic --处理运行时恐慌

3.1示例:

3.2 panic 函数

四、go 语句

4.1 示例1: go 语句不被等待----未等到运行,主程序结束

4.2 示例2 :time.Sleep()

4.3 示例3:runtime.Gosched() 让当前Goroutine go实体休息

4.4 示例4: sync.WaitGroup 操作更多Goroutine go实体

4.5作业:三个Goroutine 并发,固定顺序执行

一、defer

defer语句仅能被放置在函数或方法中。

  • 它由关键字defer和一个调用表达式组成
    • 1、调用表达式不能是 对 go 内建函数的调用;
    • 2、也不能是对 Go语言标准代码包 unsafe 中的那些函数的调用
  • 一个函数中有多个defer ,那么defer携带的表达式执行顺序 是其 出现顺序 的倒序

作用:

1、延迟执行——(js里的 延迟加载文件 好像也是defer)

当这条defer语句被执行的时候,其中的这条表达式语句并不会被立即执行。它的确切的执行时机是在其所属的函数(这里是readFile)的执行即将结束的那个时刻

2、在文件操作中可以起到保险措施,文件读取完毕后 关闭文件流。

1.1 示例--文件操作-确保文件流关闭

func readFile(path string) ([]byte, error) {

file, err := os.Open(path)

if err != nil {

return nil, err

}

defer file.Close()

return ioutil.ReadAll(file)

}

》os和ioutil(导入路径是io/ioutil)

》defer 语句: 该语句可以保证在readFile函数将结果返回给调用方之前,那个文件或目录一定会被关闭。这实际上是一种非常便捷和有效的保险措施。

 

1.2 示例:函数中多次 defer——其执行顺序为其出现顺序的倒序。

func deferIt() {

defer func() {

fmt.Print(1)

}()

defer func() {

fmt.Print(2)

}()

defer func() {

fmt.Print(3)

}()

fmt.Print(4)

}

 

1.3 defer 特别提示

  • defer fmt.Print(i+1) ——其中的 i+1 会在 执行到defer 的时候被执行,而不是在被延迟后执行。
  • defer 调用了匿名函数。非常小心,使用了defer 之外的变量——需要作为参数传入,不能直接使用。

func deferIt4() {

for i := 1; i < 5; i++ {

defer func() {

fmt.Print(i)

}()

}

}

func deferIt4() {

for i := 1; i < 5; i++ {

defer func(n int) {

fmt.Print(n)

}(i)

}

}

 

 

 

二、Go语言异常处理——error

2.1 错误传递

error 是 Go 语言内置的一个借口类型:

type error interface{

Error() string

}

示例:

func readFile(path string) ([]byte, error) {

file, err := os.Open(path)

if err != nil {

return nil, err

}

defer file.Close()

return ioutil.ReadAll(file)

}

其中的 os.Open() 方法 即实现了Error 接口(包含有一个 返回string的Error() 函数)

 

这条if语句实际上是一条卫述语句。这样的语句会检查流程中的某个步骤是否存在异常,并在必要时中止流程并报告给上层的程序(这里是调用方)

 

2.2错误创造

(即error类型的值)并把它传递给上层程序。这很简单。只需调用标准库代码包errors的New函数即可。

if path == "" {

return nil, errors.New("The parameter is invalid!")

}

 

os.ErrPermission、io.EOF等变量的值。 都是 errors.new(“错误提示”) 创建的。

 

io.EOF ————》提示数据读取已经无更多数据可读。——应该结束文件读取

 

br:= bufio.NewReader(file)

var buf bytes.Buffer

for{

}

 

 

三、异常处理——panic --处理运行时恐慌

panic可被意译为运行时恐慌。因为它只有在程序运行的时候才会被“抛出来”。并且,恐慌是会被扩散的。当有运行时恐慌发生时,它会被迅速地向调用栈的上层传递。如果我们不显式地处理它的话,程序的运行瞬间就会被终止。这里有一个专有名词——程序崩溃。

实际上,内建函数panic和recover是天生的一对。前者用于产生运行时恐慌,而后者用于“恢复”它。不过要注意,recover函数必须要在defer语句中调用才有效。因为一旦有运行时恐慌发生,当前函数以及在调用栈上的所有代码都是失去对流程的控制权。

 

只有defer语句携带的函数中的代码才可能在运行时恐慌迅速向调用栈上层蔓延时“拦截到”它。这里有一个可以起到此作用的defer语句的示例:

 

3.1示例:

defer func() {

if p := recover(); p != nil {

fmt.Printf("Fatal error: %s\n", p)

}

}()

在这条defer语句中,我们调用了recover函数。该函数会返回一个interface{}类型的值。还记得吗?interface{}代表空接口。Go语言中的任何类型都是它的实现类型。我们把这个值赋给了变量p。如果p不为nil,那么就说明当前确有运行时恐慌发生。这时我们需根据情况做相应处理。注意,一旦defer语句中的recover函数调用被执行了,运行时恐慌就会被恢复,不论我们是否进行了后续处理。所以,我们一定不要只“拦截”不处理。

 

 

3.2 panic 函数

人为引发运行时恐慌的方式是调用panic函数

 

  我们下面来反观panic函数。该函数可接受一个interface{}类型的值作为其参数。也就是说,我们可以在调用panic函数的时候可以传入任何类型的值。不过,我建议大家在这里只传入error类型的值。这样它表达的语义才是精确的。更重要的是,当我们调用recover函数来“恢复”由于调用panic函数而引发的运行时恐慌的时候,得到的值正是调用后者时传给它的那个参数。因此,有这样一个约定是很有必要的。

 

package main

 

import (

"errors"

"fmt"

)

 

func innerFunc() {

fmt.Println("Enter innerFunc")

panic(errors.New("Occur a panic!"))

fmt.Println("Quit innerFunc")

}

 

func outerFunc() {

fmt.Println("Enter outerFunc")

innerFunc()

fmt.Println("Quit outerFunc")

}

 

func main() {

fmt.Println("Enter main")

defer func() {

if e := recover(); e != nil {

fmt.Printf("Fatal error: %s\n", e)

}

}()

outerFunc()

fmt.Println("Quit main")

 

}

心得:

必须要再 panic 恐慌前 声明恢复。

 

 

 

四、go 语句

参考文档——《Go并发编程实战》

go 语句、通道类型是 Go 语言的并发编程理念的最终体现。

  • go函数的执行时间的不确定性
  • time.Sleep 来协调其执行。——简单粗暴
  • main函数的最后调用runtime.Gosched函数。——让当前
  • sync 的Add() 内置整数增加 ; Done() 减1 ; Wait() 使当前Goroutine 阻塞,直到整数为0

与defer语句相同,go语句也可以携带一条表达式语句。

 

go语句的执行会很快结束,并不会对当前流程的进行造成阻塞或明显的延迟。

 

在go语句被执行时,其携带的函数(也被称为go函数)以及要传给它的若干参数(如果有的话)会被封装成一个实体(即Goroutine),并被放入到相应的待运行队列中。Go语言的运行时系统会适时的从队列中取出待运行的Goroutine并执行相应的函数调用操作。注意,对传递给这里的函数的那些参数的求值会在go语句被执行时进行。这一点也是与defer语句类似的。

 

4.1 示例1: go 语句不被等待----未等到运行,主程序结束

package main

 

import (

"fmt"

)

 

func main() {

go fmt.Println("Go!")

}  

这样一个命令源码文件被运行时,标准输出上不会有任何内容出现。因为还没等Go语言运行时系统调度那个go函数执行,主函数main就已经执行完毕了。函数main的执行完毕意味着整个程序的执行的结束。因此,这个go函数根本就没有执行的机会。

4.2 示例2 :time.Sleep()

 

package main

 

import (

"fmt"

"time"

)

 

func main() {

go fmt.Println("Go!")

time.Sleep(100 * time.Millisecond)

}

    语句time.Sleep(100 * time.Millisecond)会把main函数的执行结束时间向后延迟100毫秒。100毫秒虽短暂,但足够go函数被调度执行的了。上述命令源码文件在被运行时会如我们所愿地在标准输出上打印出Go!。

 

 

4.3 示例3:runtime.Gosched() 让当前Goroutine go实体休息

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

 

4.4 示例4: sync.WaitGroup 操作更多Goroutine go实体

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

    sync.WaitGroup类型有三个方法可用——Add、Done和Wait。Add会使其所属值的一个内置整数得到相应增加,Done会使那个整数减1,而Wait方法会使当前Goroutine(这里是运行main函数的那个Goroutine)阻塞直到那个整数为0。这下你应该明白上面这个示例所采用的方法了。我们在main函数中启用了三个Goroutine来封装三个go函数。每个匿名函数的最后都调用了wg.Done方法,并以此表达当前的go函数会立即执行结束的情况。当这三个go函数都调用过wg.Done函数之后,处于main函数最后的那条wg.Wait()语句的阻塞作用将会消失,main函数的执行将立即结束。

 

 

4.5作业:三个Goroutine 并发,固定顺序执行

  • 实现方法一: sync.WaitGroup 来实现操作
  • 实现方法二: 用通道实现自然阻塞来实现。

 

代码:

package main

 

 

 

import (

 

    "fmt"

 

    "sync"

 

)

 

 

 

func main() {

 

    var wg sync.WaitGroup

 

    wg.Add(1)

 

    go func() {

 

        fmt.Println("1")

 

        wg.Done()

 

    }()

 

    wg.Wait()

 

    wg.Add(1)

 

    go func() {

 

        fmt.Println("2")

 

        wg.Done()

 

    }()

 

    wg.Wait()

 

    wg.Add(1)

 

    go func() {

 

        fmt.Println("3")

 

        wg.Done()

 

    }()

 

    wg.Wait()

 

    //---方法二-----------//用通道实现自然阻塞

 

    ch1 := make(chan int, 1)

 

    ch2 := make(chan int, 1)

 

    ch3 := make(chan int, 3)

 

    go func() {

 

        fmt.Println("1")

 

        ch1 <- 1

 

    }()

 

    go func() {

 

        <-ch1

 

        fmt.Println("2")

 

        ch2 <- 2

 

    }()

 

    go func() {

 

        <-ch2

 

        fmt.Println("3")

 

        ch3 <- 3

 

    }()

 

    <-ch3

 

}

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值