goroutine
goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。你将会发现,它的
使用出人意料得简单。
假设我们需要实现一个函数Add(),它把两个参数相加,并将结果打印到屏幕上,具体代码
如下:
func Add(x, y int) {
z := x + y
fmt.Println(z)
}
那么,如何让这个函数并发执行呢?具体代码如下:
go Add(1, 1)
是不是很简单?
你应该已经猜到,“go”这个单词是关键。与普通的函数调用相比,这也是唯一的区别。的
确, go是Go语言中最重要的关键字,这一点从Go语言本身的命名即可看出。
在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用
的函数返回时,这个goroutine也自动结束了。需要注意的是,如果这个函数有返回值,那么这个
返回值会被丢弃。
案例分析:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
//如果此处不加sleep则go say("world")将无打印输出
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
Go
程序从初始化
main package
并执行
main()
函数开始,当
main()
函数返回时,程序退出,且程序并不等待其他
goroutine
(非主
goroutine
)结束。
channel
channel 是有类型的管道,可以用 channel 操作符 <-
ch <- v // 将 v 送入 channel ch。 v := <-ch // 从 ch 接收,并且赋值给 v。
(“箭头”就是数据流的方向。)
和 map 与 slice 一样,channel 使用前必须创建:
ch := make(chan int)
默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。
package main
import "fmt"
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // 将和送入 c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[len(a)/2:], c)
go sum(a[:len(a)/2], c)
x, y := <-c, <-c // 从 c 中获取,后进先出,输出结果17 -5 12
fmt.Println(x, y, x+y)
}
缓冲 channel
channel 可以是 带缓冲的。为 make
ch := make(chan int, 100)
向带缓冲的 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。 而当缓冲区为空的时候接收操作会阻塞。
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
// ch <- 3 //送入channel超出缓冲区,编译时会报错all goroutines are asleep - deadlock!
fmt.Println(<-ch)
fmt.Println(<-ch)
// fmt.Println(<-ch) //从channel取出值时超出缓冲区或缓冲区为空都会报错all goroutines are asleep - deadlock!
}
range 和 close
发送者可以 close
v, ok := <-ch
之后 ok 会被设置为 false。
循环 `for i := range c` 会不断从 channel 接收值,直到它被关闭。
*注意:* 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。 *还要注意:* channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 range。
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c) //此处如果不关闭channel,在下边使用range遍历到末尾时还会继续取而导致超出缓冲区,报错all goroutines are asleep - deadlock!
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
select
select
select
select
{
case
<-chan1:
//
如果
chan1
成功读到数据,则进行该
case
处理语句
case
chan2 <- 1:
//
如果成功向
chan2
写入数据,则进行该
case
处理语句
default
:
//
如果上面都没有成功,则进入
default
处理流程
}
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
案例二
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}