无缓冲通道
Goroutine 之间的同步交接
无缓冲通道 是 CoddyKit 上的免费 Go Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Go Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Go Academy 课程共包含 4 节课。
什么是通道
通道是用于在 goroutine 之间通信的类型化管道。通道遵循 Go 的箴言:不要通过共享内存进行通信;应通过通信来共享内存。
package main
import "fmt"
func main() {
ch := make(chan int) // unbuffered int channel
go func() {
ch <- 42 // send: blocks until someone receives
}()
v := <-ch // receive: blocks until someone sends
fmt.Println(v) // 42
}无缓冲通道的语义
无缓冲通道没有容量。发送操作会阻塞,直到接收方准备就绪;接收操作会阻塞,直到发送方发送数据。这提供了有保障的同步交接:
package main
import "fmt"
func double(in <-chan int, out chan<- int) {
for v := range in {
out <- v * 2
}
close(out)
}
func main() {
in := make(chan int)
out := make(chan int)
go double(in, out)
go func() { in <- 3; in <- 5; close(in) }()
for v := range out { fmt.Println(v) }
// 6
// 10
}将通道用作同步机制
无缓冲通道可以同步 goroutine,可将其用作工作完成的信号:
package main
import "fmt"
func compute(result chan<- int) {
sum := 0
for i := 1; i <= 100; i++ { sum += i }
result <- sum
}
func main() {
ch := make(chan int)
go compute(ch)
fmt.Println("Result:", <-ch) // blocks until compute sends
}关闭通道
发送方使用close(ch)关闭通道,以表示不会再发送更多值。接收方通过双值形式检测通道是否已关闭:
package main
import "fmt"
func generate(ch chan<- int, max int) {
for i := 0; i < max; i++ {
ch <- i
}
close(ch) // signal done
}
func main() {
ch := make(chan int)
go generate(ch, 5)
for v := range ch { // range detects close automatically
fmt.Println(v)
}
fmt.Println("channel closed")
}双值接收
双值接收v, ok := <-ch会告诉您通道是否仍处于打开状态:
package main
import "fmt"
func main() {
ch := make(chan string, 1)
ch <- "hello"
close(ch)
v1, ok1 := <-ch
fmt.Println(v1, ok1) // hello true
v2, ok2 := <-ch
fmt.Println(v2, ok2) // "" false — channel closed
}向已关闭的通道发送数据会触发崩溃
向已关闭的通道发送数据会触发崩溃。只有发送方应该关闭通道,接收方绝不能关闭:
package main
import "fmt"
func main() {
ch := make(chan int)
close(ch)
// Receiving from closed channel returns zero value
v, ok := <-ch
fmt.Println(v, ok) // 0 false
// Sending to closed channel panics:
defer func() { recover() }()
ch <- 1 // panic: send on closed channel
}单向通道
为了提高代码的清晰性和安全性,可以在函数签名中将通道限制为只发送(chan<- T)或只接收(<-chan T):
package main
import "fmt"
func producer(out chan<- int) { // send-only
for i := 0; i < 3; i++ { out <- i }
close(out)
}
func consumer(in <-chan int) { // receive-only
for v := range in { fmt.Println(v) }
}
func main() {
ch := make(chan int)
go producer(ch)
consumer(ch)
}goroutine 与通道的往返通信
两个 goroutine 通过通道来回传递消息,这是经典的同步示例:
package main
import "fmt"
func ping(ping, pong chan struct{}) {
for i := 0; i < 3; i++ {
<-ping
fmt.Println("ping")
pong <- struct{}{}
}
}
func main() {
ping := make(chan struct{})
pong := make(chan struct{})
go func() { for { <-pong; fmt.Println("pong"); ping <- struct{}{} } }()
go func() { for i := 0; i < 3; i++ { ping <- struct{}{}; <-pong } }()
// Simplified version:
fmt.Println("channels enable structured communication")
}nil 通道会永久阻塞
从nil通道接收或向其发送数据都会永久阻塞。有时会在选择语句中有意使用这一特性:
package main
import "fmt"
func main() {
var ch chan int // nil channel
fmt.Println(ch == nil) // true
// select with nil channel — that case is never selected
active := make(chan int, 1)
active <- 42
select {
case v := <-ch: // never selected (nil)
fmt.Println(v)
case v := <-active: // selected
fmt.Println("got:", v)
}
}何时使用无缓冲通道
在以下情况下可以使用无缓冲通道:
- 需要有保障的同步交接
- goroutine 必须确认已收到数据后,发送方才能继续
- 实现信号量或反压
- 构建流水线,并且每个阶段都应一次处理一个项目
快速检查
向没有准备好接收方的无缓冲通道发送数据时会发生什么?
回顾:无缓冲通道
摘要:
- 使用
make(chan T)创建——容量为零 - 发送会阻塞,直到接收方准备就绪;接收会阻塞,直到发送方发送数据
- 只有发送方应该关闭通道
range ch会迭代,直到通道关闭- 双值接收:
v, ok := <-ch - 使用单向类型提高代码清晰性:
chan<-和<-chan
常见问题解答
「无缓冲通道」课时是免费的吗?
是的 — 「无缓冲通道」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Go Academy 课程的其余内容,请升级到 CoddyKit PRO。 Go Academy 课程共包含 4 节课。
「无缓冲通道」这节课中我会学到什么?
Goroutine 之间的同步交接 你通过在浏览器中直接运行的动手代码来练习 Go Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Go Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Go Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「无缓冲通道」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Go Academy 课中编写并运行代码吗?
能。每节 Go Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。