Go语言程序设计-第8章–goroutine 和通道
8.1 goroutine
当程序启动时,只有一个 goroutine 来调用 main 函数,称为主 goroutine. 新的 goroutine 需要通过 go 语句进行创建。在普通的函数或者方法调用前边加上 go 关键字。
f() // 调用f
go f() // 新建一个调用 f() 的 goroutine,不用等待。
8.4 通道
goroutine 是 Go 程序的并发的执行体,通道就是他们之间的连接。通道是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。每个通道是一个具体类型的导管,叫作通道的元素类型。一个有 int 类型元素的通道写为 chan int.
使用内置的 make 函数来创建一个通道:
ch := make(chan int)
通道的零值是 nil。同种类型的通道可以使用 == 符号进行比较。
通道主要有两个主要操作:发送和接收,两者统称为通信。
ch <- x // 发送语句
x = <- ch //赋值语句中的接收表达式
通道支持 close, 用法 close(ch)
。如果 close 之后,再执行发送操作,会导致宕机。如果在一个已经关闭的通道上执行读操作,将获取所有已经发送的值,直到通道为空;这时任何接收操作会立即完成,同时获取到一个通道元素类型对应的零值。
创建通道是可以接受第二个可选参数,一个表示通道容量的整数。如
ch = make(chan int) // 无缓冲通道
ch = make(chan int, 0) // 无缓冲通道
ch = make(chan int, 3) // 容量为 3 的缓冲通道
8.4.1 无缓冲通道
无缓冲通道上的发送操作将会阻塞,直到另一个 goroutine 的对应的通道上执行接收操作,这时值传送完成,两个 goroutine 都可以继续执行。相反,如果接收操作先执行,接收方 goroutine 将阻塞,直到另一个 goroutine 在同一个通道上发送一个值。
8.4.2 管道
通道可以用来连接 goroutine,这样一个的输出是另一个的输入,这个叫作管道(pipeline)。
结束时,关闭每个通道不是必须的。通道可以通过垃圾回收器根据它是否可以访问来决定是否回收它。试图关闭一个已经关闭的通道会导致宕机。
8.4.3 单向通道类型
package main
import "fmt"
//!+
func counter(out chan<- int) {
for x := 0; x < 100; x++ {
out <- x
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for v := range in {
out <- v * v
}
close(out)
}
func printer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
}
func main() {
naturals := make(chan int)
squares := make(chan int)
go counter(naturals)
go squarer(squares, naturals)
printer(squares)
}
8.4.4 缓冲通道
如果无缓冲通道,发送的 goroutine 发送数据到通道时,如果没有 goroutine 来接收,这个情况叫作 goroutine 泄露。不像回收变量,泄露的 goroutine 不会自动回收。
8.7 使用 select 多路复用
select {
case <- ch1
//
case x <- ch2
//
case ch3 <- y :
//
default
}
select 和 switch 语句一样,它有一系列的情况和一个可选的默认分支。每一个情况指定一次通信和关联的一段代码。
select 一直等待,直到一次通信来告知有一些情况可以执行。如果多个情况同时满足,select 随机选择一个,这样保证每个通道有相同的机会被选中。
非阻塞通道
下面的select 执行时会立即返回,不论通道里有没有数据。
select {
case <- abort:
fmt.Printf("Launch aborted !\n")
return
default:
// 不执行任何操作
}
8.9 取消
我们需要一个可靠的机制,在一个通道上广播一个事件,这样很多 goroutine 可以认为它已经发生了,然后可以看到他已经发生。
当一个通道已经关闭并且读完所有的发送的值后,接下来的读操作立即返回。
var done = make(chan struct{})
func cancelled() bool {
select {
case <- done:
return true
default:
return false
}
}
go func() {
os.Stdin.Read(make([]byte, 1))
close(done)
}()