Go语言笔记 - Channel
1. 什么是channel
Channel 是 Go 语言中用来在 goroutine 之间传递数据的通信机制,本质上像一个管道(队列),可以安全地在线程之间传数据,避免加锁。
通俗点说,Channel 就是 goroutine 之间用来**发送(send)和接收(receive)**数据的桥梁。
2. 并发模型:Goroutine + Channel
CSP(Communicating Sequential Processes)是一种“用通信代替共享内存”的并发模型
Go 就是这么设计的,用 channel 通信,不用锁、不共享数据。
3. 如何使用channel
这里将举例如何使用channel进行消息产出、消费等。
创建channel
ch := make(chan int) // 创建一个传 int 的 channel
发送数据
ch <- 10 // 把 10 发送到 ch
接受数据
value := <-ch // 从 ch 接收一个值
完整实例
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hello from goroutine" // 子 goroutine 发送
}()
msg := <-ch // 主 goroutine 接收
fmt.Println(msg)
}
4. channel分类
类型 | 特点 | 用途示例 |
---|---|---|
无缓冲 channel | 发送/接收必须同时进行 | 严格同步通信 |
有缓冲 channel | 缓冲没满可继续发送 | 缓解同步压力 |
close + range | 自动读取直到结束 | 读取所有数据,像读切片 |
select 多路复用 | 哪个 channel 来就处理谁 | 并发处理多个任务,超时控制等 |
无缓冲channel(默认)
发送与接收必须同时进行,否则会阻塞
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 1 // 没有接收者会卡在这
}()
val := <-ch
fmt.Println("收到:", val)
}
有缓冲channel
缓冲区没满时,发送不会阻塞。
缓冲区满时,发送阻塞。
接收可以在发生之后再进行。
package main
import "fmt"
func main() {
ch := make(chan int, 2) // 容量为 2
ch <- 1
ch <- 2
// ch <- 3 // 再发就会阻塞或报错(缓冲满了)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
关闭channel和range
range会一直读直到channel关闭
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
close(ch) // 表示不再发送数据了
for val := range ch {
fmt.Println("收到:", val)
}
}
select多路复用
同时监听多个channel
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "来自 ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "来自 ch2"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
case <-time.After(3 * time.Second):
fmt.Println("超时")
}
}
5. select高级用法
非阻塞操作
// 发送
select {
case ch <- 1:
fmt.Println("发送成功")
default:
fmt.Println("channel 满,发送失败")
}
// 接收
select {
case val := <-ch:
fmt.Println("收到:", val)
default:
fmt.Println("没有数据可收")
}
超时控制
select {
case res := <-ch:
fmt.Println("收到:", res)
case <-time.After(2 * time.Second):
fmt.Println("超时退出")
}
同时监听多个 channel
select {
case v1 := <-ch1:
fmt.Println("来自 ch1:", v1)
case v2 := <-ch2:
fmt.Println("来自 ch2:", v2)
default:
fmt.Println("两个 channel 都没准备好")
}
检测 channel 是否关闭
v, ok := <-ch
if !ok {
fmt.Println("channel 已关闭")
}
6. channel死锁
死锁就是当 goroutine 被永久阻塞,比如等不到数据或者收不到数据,就会死锁。
场景 1:没有接收方
func main() {
ch := make(chan int)
ch <- 1 // 阻塞等待,但没人接收,程序卡死,panic: deadlock
}
场景 2:channel 关闭后还发送数据
func main() {
ch := make(chan int, 1)
close(ch)
ch <- 1 // panic: send on closed channel
}
7. 注意事项
禁止向已关闭的 channel 发送数据
func main() {
ch := make(chan int)
close(ch)
ch <- 1 // ❗panic: send on closed channel
}
禁止重复关闭channel
func main() {
ch := make(chan int)
close(ch)
close(ch) // ❗panic: close of closed channel
}
禁止关闭或操作nil channel
nil channel 没法被关闭、也不能发送接收,否则会一直阻塞,或直接 panic
func main() {
var ch chan int // nil
// ch <- 1 // ❗panic: send on closed channel
// <-ch // ❗panic: send on closed channel
close(ch) // ❗panic: close of nil channel
}
从关闭的 channel 接收数据(合法,但可能要判断)
从关闭的 channel 中读取数据是安全的,只是会读到“零值”,并且
ok == false
。
func main() {
ch := make(chan int)
close(ch)
v, ok := <-ch
fmt.Println(v, ok) // 👉 0 false(ok=false 说明 channel 关闭了)
}
接收使用range,必须关闭channel
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
// close(ch) // 关闭channel
for val := range ch {
fmt.Println("收到:", val) // 死循环
}
}
原则
谁创建、谁关闭;谁发送、谁不关闭;谁接收、谁不关心关闭。
什么情况下会触发资源泄漏
channel是有可能引发goroutine泄漏的。
goroutine使用操作channel后,会处于发送或接收的阻塞状态,如果此时channel一直处于满或空状态不改变,goroutine就会一直阻塞,不会结束也不会垃圾回收器回收
避坑小技巧
- 写入时,确保channel未关闭
- 确保关闭操作只有唯一操作者且只能执行一次
- 操作前确保channel已完成初始化
- 接收方使用range,必须关闭channel