Go 语言 在线

2180Go 并发

更好的展示边入边出概念:

package main

import (
    "fmt"
    "time"
)

func main() {
    
    c := make(chan int, 10)
    
    go fibonacci(cap(c), c)
    
    for v := range c {
        fmt.Println("out:", time.Now())
        fmt.Println(v)
    }
}

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    
    for i :=0; i < n; i++ {
        c <- x
        fmt.Println("in:",time.Now())
        time.Sleep(100)
        x, y = y, x+y
    }
    
    close(c)
}

2179Go 并发

我们单独写一个 say2 函数来跑 goroutine,并且 Sleep 时间设置长一点,150 毫秒,看看会发生什么:

package main
import (
    "fmt"
    "time"
)
func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s, (i+1)*100)
    }
}
func say2(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(150 * time.Millisecond)
        fmt.Println(s, (i+1)*150)
    }
}
func main() {
    go say2("world")
    say("hello")
}

输出结果:

hello 100
world 150
hello 200
hello 300
world 300
hello 400
world 450
hello 500

[Done] exited with code=0 in 2.066 seconds

问题来了,say2 只执行了 3 次,而不是设想的 5 次,为什么呢?

原来,在 goroutine 还没来得及跑完 5 次的时候,主函数已经退出了。

我们要想办法阻止主函数的结束,要等待 goroutine 执行完成之后,再退出主函数:

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s, (i+1)*100)
    }
}
func say2(s string, ch chan int) {
    for i := 0; i < 5; i++ {
        time.Sleep(150 * time.Millisecond)
        fmt.Println(s, (i+1)*150)
    }
    ch <- 0
    close(ch)
}

func main() {
    ch := make(chan int)
    go say2("world", ch)
    say("hello")
    fmt.Println(<-ch)
}

我们引入一个信道,默认的,信道的存消息和取消息都是阻塞的,在 goroutine 中执行完成后给信道一个值 0,则主函数会一直等待信道中的值,一旦信道有值,主函数才会结束。

2178Go 并发

goroutine 是 golang 中在语言级别实现的轻量级线程,仅仅利用 go 就能立刻起一个新线程。多线程会引入线程之间的同步问题,在 golang 中可以使用 channel 作为同步的工具。

通过 channel 可以实现两个 goroutine 之间的通信。

创建一个 channel, make(chan TYPE {, NUM}) TYPE 指的是 channel 中传输的数据类型,第二个参数是可选的,指的是 channel 的容量大小。

向 channel 传入数据, CHAN <- DATA , CHAN 指的是目的 channel 即收集数据的一方, DATA 则是要传的数据。

从 channel 读取数据, DATA := <-CHAN ,和向 channel 传入数据相反,在数据输送箭头的右侧的是 channel,形象地展现了数据从隧道流出到变量里。

2177Go 错误处理

个人多次试验,总结几点 panic,defer 和 recover。

  • 1、panic 在没有用 recover 前以及在 recover 捕获那一级函数栈,panic 之后的代码均不会执行;一旦被 recover 捕获后,外层的函数栈代码恢复正常,所有代码均会得到执行;
  • 2、panic 后,不再执行后面的代码,立即按照逆序执行 defer,并逐级往外层函数栈扩散;defer 就类似 finally;
  • 3、利用 recover 捕获 panic 时,defer 需要再 panic 之前声明,否则由于 panic 之后的代码得不到执行,因此也无法 recover;
package main

import (
"fmt"
)

func main() {
  fmt.Println("外层开始")
  defer func() {
    fmt.Println("外层准备recover")
    if err := recover(); err != nil {
      fmt.Printf("%#v-%#v\n", "外层", err) // err已经在上一级的函数中捕获了,这里没有异常,只是例行先执行defer,然后执行后面的代码
    } else {
      fmt.Println("外层没做啥事")
    }
    fmt.Println("外层完成recover")
  }()
  fmt.Println("外层即将异常")
  f()
  fmt.Println("外层异常后")
  defer func() {
    fmt.Println("外层异常后defer")
  }()
}

func f() {
  fmt.Println("内层开始")
  defer func() {
    fmt.Println("内层recover前的defer")
  }()

  defer func() {
    fmt.Println("内层准备recover")
    if err := recover(); err != nil {
      fmt.Printf("%#v-%#v\n", "内层", err) // 这里err就是panic传入的内容
    }

    fmt.Println("内层完成recover")
  }()

  defer func() {
    fmt.Println("内层异常前recover后的defer")
  }()

  panic("异常信息")

  defer func() {
    fmt.Println("内层异常后的defer")
  }()

  fmt.Println("内层异常后语句") //recover捕获的一级或者完全不捕获这里开始下面代码不会再执行
}

代码执行的结果:

外层开始
外层即将异常
内层开始
内层异常前recover后的defer
内层准备recover
"内层"-"异常信息"
内层完成recover
内层recover前的defer
外层异常后
外层异常后defer
外层准备recover
外层没做啥事
外层完成recover

2176Go 错误处理

fmt.Println 打印结构体的时候,会把其中的 error 的返回的信息打印出来。

type User struct {
   username string
   password string
}

func (p *User) init(username string ,password string) (*User,string)  {
   if ""==username || ""==password {
      return p,p.Error()
   }
   p.username = username
   p.password = password
   return p,""}

func (p *User) Error() string {
      return "Usernam or password shouldn't be empty!"}
}

func main() {
   var user User
   user1, _ :=user.init("","");
   fmt.Println(user1)
}

结果:

Usernam or password shouldn't be empty!