Go语言中通道存在两种:非缓冲通道和缓冲通道。
非缓冲通道语法如下:
channel := make(chan type) // type 为通道类型(值可以为 string, int...)。
缓冲通道语法相比非缓冲通道多了一个参数,用于指定缓冲队列的大小。示例如下:
channel := make(chan type, size) // size 为队列大小。
非缓冲通道的发送和接收都是阻塞的。协程尝试从通道中拿数据会导致阻塞,直到有数据到达。协程向通道中发送数据也会导致阻塞,直到数据被提取。
缓冲通道有一个队列,队列大小取决于你指定的容量。发送操作会将数据放入队列尾部,接收操作会从队头取出数据。向一个满队列发送数据会导致阻塞,直到队列不满。从一个空队列取数据也会阻塞,直到队列不空。
我们可以简单得将非缓冲通道理解为队列容量为零的缓冲通道。
非缓冲通道有一种常见的用途就是通道同步。
示例如下:
package main
import (
"fmt"
"time"
)
func worker(done chan bool) {
fmt.Println("working...")
// 假装很忙,忙两秒。
time.Sleep(2 * time.Second)
// 忙完了。
fmt.Println("finished")
// 向通道发送任务完成信息
done <- true
}
func main() {
// 创建非缓冲通道
done := make(chan bool)
// 开启协程
go worker(done)
// 阻塞等待完成信息。
<-done
}
上面的通道同步就是利用到了非缓冲通道接收信息时阻塞的特性。
缓冲通道有一个用途就是实现生产者消费者模型。利用缓冲通道的缓冲队列我们可以轻松实现生产者的异步发送和消费者的异步接收。
下面我们使用缓冲通道实现一个最基本的生产者消费者模型,示例如下:
package main
import (
"fmt"
"time"
)
// 生产者:向缓冲通道队列中生产数据。
func producer(message chan int) {
var count int
for {
message <- count
count++
}
}
// 消费者:从缓冲通道队列中消费数据。
func consumer(message chan int) {
for {
fmt.Println("recv:", <-message)
}
}
func main() {
// 创建缓冲队列大小为200的缓冲通道。
message := make(chan int, 200)
go producer(message)
go consumer(message)
// 阻塞1s,给程序一定运行时间。
time.Sleep(1 * time.Second)
}
在这个例子中,producer
函数是生产者,它不断地向通道message
发送整数。consumer
函数是消费者,它不断地从通道message
接收数据并打印。
当生产者的生产速度大于消费者的消费速度时,会将队列填满而阻塞生产。当消费者的消费速度大于生产者的生产速度时,会将队列耗空而阻塞消费。自然而然得控制住了生产者和消费者的处理速度。