一、Channel 设计原理
Go 中使用 Channel 即通信的方式共享内存,避免了使用内存共享的方式通信,解决了需要限制同一时间能够读写变量的线程数量去解决线程冲突的问题。
Go 语言虽然也能使用 共享内存+互斥锁 进行通信,但同时也提供了 通信顺序进程(Communicating sequential processes,CSP) 的并发模型。即,Goroutine -> Channel -> Goroutine
二、非缓冲信道 - FIFO 同步
默认情况的 Channel 为非缓冲信道,使用 先入先出(FIFO) 处理收发。
- 先从 Channel 读取数据的 Goroutine 会先接收到数据;
- 先向 Channel 发送数据的 Goroutine 会得到先发送数据的权利;
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
三、缓冲信道 - 异步
- 不遵循 FIFO,发送端和接受端的数据获取处于异步状态
- 发送方 会向缓冲区中写入数据,然后唤醒接收方,多个接收方会尝试从缓冲区中读取数据,如果没有读取到就会重新陷入休眠;
- 接收方 会从缓冲区中读取数据,然后唤醒发送方,发送方会尝试向缓冲区写入数据,如果缓冲区已满就会重新陷入休眠;
- 若缓冲区满载,则会导致发送方阻塞等待
- Channel 内无值,导致接收方阻塞等待
package main
import "fmt"
func main() {
// 指定缓冲区大小
// 仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
四、Channel 的 range 和 close
c := make(chan int, 10)
// range
for v,ok := range c {
fmt.Println(v,ok)
}
close(c)
// 关闭信道,禁止数据流入,但可读。
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)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
五、nil Channels
一个为 nil 的 channel,发送和接受操作都会被永久阻塞。
package main
import (
"fmt"
"time"
)
func main() {
var ch chan int
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}
//get first result
fmt.Println("result:",<-ch)
//do other work
time.Sleep(2 * time.Second)
}
// fatal error: all goroutines are asleep - deadlock!