14.2 Channel
14.2 Channel
14.2.1 概念
通道(Channel):
- 负责协程间的通讯;
声明
var identifier chan datatype
默认零值为
nil
使用
make()
初始化
ch := make(chan datatype)
<-
14.2.2 通信操作符 信息按照箭头的方向流动:
流向通道(发送)
ch <- int1
将变量
int1
的值发送至ch
从通道流出(接收)
int2 = <- ch
从
ch
中获取数据并赋值给int2
单独调用
<- ch
获取下一个值,可用于验证:if <- ch != 1 { //... }
通道的发送和接收都是原子操作
package chapter_14
import "fmt"
func gr() {
ch := make(chan string)
go sendString(ch)
go getString(ch)
}
func sendString(ch chan string) {
ch <- "A"
ch <- "B"
ch <- "C"
ch <- "D"
ch <- "E"
}
func getString(ch chan string) {
var s string
for {
s = <-ch
fmt.Println(s)
}
}
A
B
C
D
E
注意:不要使用打印状态来表明通道的发送和接收顺序:由于打印状态和通道实际发生读写的时间延迟会导致和真实发生的顺序不同。
14.2.3 通道阻塞
无缓冲的channel的发送/接受操作在对方准备好之前是阻塞的:
- 同一个通道,发送在接受者准备好之前是阻塞的,即等待channel再次变为可用状态(通道值被接收时,可以传入变量)
- 同一个通道,接收操作是阻塞的(通道中没有数据),直到发送者向通道中发送数据
package chapter_14
import (
"fmt"
"time"
)
func chblock() {
ch := make(chan int)
go pump(ch)
go suck(ch)
fmt.Println(<-ch)
time.Sleep(time.Second)
}
func pump(out chan int) {
for i := 0; i < 21; i++ {
out <- i
}
}
0
pump
: 向通道中发送数据,但因fmt.Println(<-ch)
之后没有接受者将会阻塞,故仅输出一个数据0
定义suck
函数在无限循环中读取:
func suck(in chan int) {
for {
fmt.Printf("%d, ", <-in)
}
}
func chblock() {
ch := make(chan int)
go pump(ch)
go suck(ch)
//fmt.Println(<-ch)
time.Sleep(time.Second)
}
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
14.2.4 通过通道交换数据进行协程同步
通过通道(channel) , 协程可以在通信中某刻同步交换数据。
若在通道两端互相阻塞对方则会形成死锁,Go 会检测并触发panic
func dl() {
ch := make(chan int)
ch <- 1
go fn(ch)
}
func fn(in chan int) {
fmt.Println(<-in)
}
上述例子中ch
两端的协程均休眠,形成死锁
14.2.5 带缓冲的通道
无缓冲的通道容量为1,可使用make()
设置通道容量
make(chan datatype, buf_size)
buf_size
是通道可以同时容纳的元素个数。
- 在缓冲满载之前,带缓冲的通道发送数据时不会阻塞。
- 在缓冲变空之前,带缓冲的通道接收数据时不会阻塞。
内置cap()
可获取通道容量。
若容量大于0,通道是异步的:缓冲满载(发送)/变空(接收)之前不会阻塞,元素按照FIFO(先进先出)的顺序接收。
若容量为0(无缓冲),通道是同步的:通信仅在双方准备好时才可成功。
ch := make(chan type, buf_size)
value
:
0
: synchronous, unbuffered>0
: asynchronous, buffered
14.2.6 信号量模式
使用信号量(semaphore)模式通过通道进行同步。
例如从通道中获取处理结果:
func compute(ch chan int){
ch <- someComputation() // when it completes, signal on the channel.
}
func main(){
ch := make(chan int) // allocate a channel.
go compute(ch) // start something in a goroutines
doSomethingElseForAWhile()
result := <- ch
}
14.2.7 通道工厂模式
不将通道作为参数传给协程,而是利用函数生成通道并返回,函数内部有匿名函数被协程调用。
package chapter_14
import (
"fmt"
"time"
)
func cf() {
ch := pump2()
go suck2(ch)
time.Sleep(time.Second)
}
func pump2() chan int {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
}()
return ch
}
func suck2(in chan int) {
for {
fmt.Println(<-in)
}
}
14.2.8 for range
可以在channel上使用for-range
:
for v := range ch {
// ...
}
从通道中读取数据直到通道关闭。
14.2.9 通道的方向
可在定义时指定通道的方向:
var send_only chan<- int
var recv_only <-chan int
只接收的通道<-chan T
无法关闭,因为关闭通道是发送者表示不再给通道发送值,故对接收通道无意义。
双向通道可赋值给单向通道:
var sch chan<- int
var rch <-chan int
ch2 := make(chan int)
sch = ch2
rch = ch2
14.2.10 管道和选择器模式
sendChan := make(chan int)
receiveChan := make(chan string)
go processChannel(sendChan, receiveChan)
func processChannel(in <-chan int, out chan<- string) {
for inValue := range in {
result := ... /// processing inValue
out <- result
}
}
通过定义通道的方向来限制对通道的操作
例子:14_2_sieve.go 通过选择器筛选素数
package chapter_14
import "fmt"
func sieve() {
ch := make(chan int)
go generate(ch)
for {
prime := <-ch
fmt.Print(prime, " ")
ch1 := make(chan int)
go filter(ch, ch1, prime)
ch = ch1
}
}
func generate(out chan<- int) {
for i := 2;; i++ {
out <- i
}
}
func filter(in <-chan int, out chan<- int, prime int) {
for {
i := <-in
if i%prime != 0 {
out <- i
}
}
}