在 Go 语言中,channel 是一种强大的并发原语,它允许不同的 goroutine 之间进行通信和同步。今天我们就来深入探讨 channel 的各个方面,并通过丰富的实例来展示其强大功能。
什么是 Channel?
Channel 是 Go 语言中实现 CSP(Communicating Sequential Processes)并发模型的核心数据结构。它类似于一个类型化的管道,可以在 goroutine 之间安全地传递数据。
Channel 的基本操作
创建 Channel
go // 无缓冲 channel ch1 := make(chan int) // 有缓冲 channel,容量为 10 ch2 := make(chan int, 10) // 只读 channel var readOnly <-chan int = ch1 // 只写 channel var writeOnly chan<- int = ch1
发送和接收数据
go ch := make(chan int, 1) // 发送数据 ch <- 42 // 接收数据 value := <-ch fmt.Println(value) // 输出: 42
Channel 类型详解
- 无缓冲 Channel
无缓冲 channel 是同步的,发送和接收操作会阻塞,直到另一端准备好。
func unbufferedChannelDemo() {
ch := make(chan string)
go func() {
fmt.Println("Goroutine: 准备发送数据")
ch <- "Hello from goroutine!"
fmt.Println("Goroutine: 数据发送完成")
}()
time.Sleep(1 * time.Second)
fmt.Println("Main: 准备接收数据")
msg := <-ch
fmt.Println("Main: 接收到数据:", msg)
time.Sleep(1 * time.Second)
}
输出:
Goroutine: 准备发送数据 Main: 准备接收数据 Main: 接收到数据: Hello from goroutine! Goroutine: 数据发送完成
- 有缓冲 Channel
有缓冲 channel 是异步的,只有在缓冲区满时发送才会阻塞,在缓冲区空时接收才会阻塞。
func bufferedChannelDemo() {
ch := make(chan int, 3)
// 发送数据不会阻塞,因为缓冲区有空间
ch <- 1
ch <- 2
ch <- 3
fmt.Println("已发送 3 个数据到缓冲区")
// 如果再发送就会阻塞,因为缓冲区已满
// ch <- 4 // 这行会阻塞
// 接收数据
fmt.Println("接收数据:", <-ch) // 1
fmt.Println("接收数据:", <-ch) // 2
fmt.Println("接收数据:", <-ch) // 3
}
Channel 的高级用法
- 使用 select 处理多个 Channel
func selectDemo() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch1 <- "来自 ch1 的消息"
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "来自 ch2 的消息"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("接收到:", msg1)
case msg2 := <-ch2:
fmt.Println("接收到:", msg2)
case <-time.After(3 * time.Second):
fmt.Println("超时!")
return
}
}
}
- 关闭 Channel 和 range 循环
func closeChannelDemo() {
ch := make(chan int, 5)
// 生产者
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 关闭 channel
}()
// 消费者 - 方法1: 使用 ok 判断
for {
if value, ok := <-ch; ok {
fmt.Printf("方法1 - 接收到: %dn", value)
} else {
fmt.Println("Channel 已关闭")
break
}
}
// 消费者 - 方法2: 使用 range
ch2 := make(chan int, 3)
go func() {
for i := 10; i < 13; i++ {
ch2 <- i
}
close(ch2)
}()
for value := range ch2 {
fmt.Printf("方法2 - 接收到: %dn", value)
}
}
- Channel 作为函数参数
// 只允许从 channel 读取
func reader(ch <-chan int) {
for value := range ch {
fmt.Println("读取:", value)
}
}
// 只允许向 channel 写入
func writer(ch chan<- int, numbers []int) {
for _, num := range numbers {
ch <- num
}
close(ch)
}
func channelAsParameter() {
ch := make(chan int, 3)
numbers := []int{1, 2, 3, 4, 5}
go writer(ch, numbers)
reader(ch)
}