Golang的并发模型基于CSP理论,channel作为goroutine之间传递数据的管道,能够避免共享内存带来的数据竞争问题,是实现并发通信的核心组件。channel分为无缓冲和有缓冲两种类型,不同的类型适用于不同的并发场景。

channel的基本定义与分类
channel是Golang内置的引用类型,使用make函数创建,声明时需要指定传递数据的类型。根据是否有缓冲区,channel可以分为两类:
- 无缓冲channel:创建时不指定缓冲区大小,发送和接收操作会同步阻塞,直到另一端准备好才会继续执行
- 有缓冲channel:创建时指定缓冲区大小,发送操作在缓冲区未满时不会阻塞,接收操作在缓冲区非空时不会阻塞
无缓冲channel的并发通信示例
无缓冲channel常用于需要严格同步的goroutine通信场景,比如一个goroutine负责生成数据,另一个goroutine负责处理数据,两者需要同步执行。
package main
import (
"fmt"
"time"
)
// 发送数据的goroutine
func sendData(ch chan string) {
fmt.Println("开始发送数据")
// 发送数据到channel,此时会阻塞直到接收端准备好
ch <- "hello channel"
fmt.Println("数据发送完成")
}
// 接收数据的goroutine
func receiveData(ch chan string) {
// 模拟处理耗时
time.Sleep(2 * time.Second)
// 从channel接收数据,此时会阻塞直到发送端发送数据
data := <-ch
fmt.Printf("接收到数据:%sn", data)
}
func main() {
// 创建无缓冲channel,传递string类型数据
ch := make(chan string)
go sendData(ch)
go receiveData(ch)
// 等待goroutine执行完成
time.Sleep(3 * time.Second)
}
上述代码中,sendData函数发送数据时会被阻塞,直到receiveData函数执行到接收操作才会继续,保证了两个goroutine的同步通信。
有缓冲channel的并发通信示例
有缓冲channel适用于生产者和消费者速度不匹配的场景,生产者可以先把数据放入缓冲区,不需要等待消费者立即处理。
package main
import (
"fmt"
"time"
)
// 生产者goroutine
func producer(ch chan int) {
for i := 1; i <= 5; i++ {
fmt.Printf("生产数据:%dn", i)
// 缓冲区未满时不会阻塞
ch <- i
time.Sleep(1 * time.Second)
}
// 生产完成后关闭channel
close(ch)
}
// 消费者goroutine
func consumer(ch chan int) {
// 循环接收channel数据,直到channel关闭
for data := range ch {
fmt.Printf("消费数据:%dn", data)
time.Sleep(2 * time.Second)
}
fmt.Println("channel已关闭,消费结束")
}
func main() {
// 创建缓冲区大小为3的有缓冲channel
ch := make(chan int, 3)
go producer(ch)
go consumer(ch)
// 等待goroutine执行完成
time.Sleep(10 * time.Second)
}
这里缓冲区大小为3,生产者前3次发送数据不会阻塞,当缓冲区满之后发送操作会阻塞,直到消费者取出数据释放缓冲区空间。
单向channel的使用
在函数参数传递时,可以使用单向channel限制channel的操作方向,避免误操作。单向channel分为只发送和只接收两种类型。
package main
import "fmt"
// 只接收的单向channel参数
func readChan(ch <-chan int) {
data := <-ch
fmt.Printf("读取到数据:%dn", data)
}
// 只发送的单向channel参数
func writeChan(ch chan<- int) {
ch <- 100
}
func main() {
ch := make(chan int, 1)
writeChan(ch)
readChan(ch)
}
上述代码中,readChan函数只能从channel接收数据,writeChan函数只能向channel发送数据,编译阶段就会限制错误操作,提升代码安全性。
channel使用的注意事项
- 不要重复关闭channel,重复关闭会触发panic
- 不要向已经关闭的channel发送数据,否则会触发panic
- 从已关闭的channel接收数据不会阻塞,会返回对应类型的零值,可以通过第二个返回值判断channel是否已关闭
- 无缓冲channel的发送和接收操作必须分别在两个不同的goroutine中执行,否则会造成死锁
package main
import "fmt"
func main() {
ch := make(chan int, 1)
ch <- 10
// 接收数据并判断channel是否关闭
data, ok := <-ch
if ok {
fmt.Printf("接收到数据:%dn", data)
} else {
fmt.Println("channel已关闭")
}
close(ch)
// 从关闭的channel接收数据
data2, ok2 := <-ch
fmt.Printf("接收到数据:%d,channel是否关闭:%vn", data2, ok2)
}