引发长时间的阻塞的情况
✅ 缓冲通道(Buffered Channel)
-
通道已满:发送操作会阻塞,直到有元素被接收。
- 阻塞的发送 goroutine 会进入一个发送等待队列,是 FIFO 的。
- 一旦有空间释放,最早被阻塞的发送方会被唤醒。
-
通道已空:接收操作会阻塞,直到有新元素写入。
- 阻塞的接收 goroutine 进入接收等待队列,也是 FIFO 的。
- 有元素写入时,会优先通知最早等待的接收方。
-
优化场景:若发送时刚好有接收者阻塞在等,那数据直接从发送方拷贝到接收方,绕过缓冲区(称为“同步发送”),提高效率。
✅ 1. 缓冲通道已满,发送操作阻塞
func main() {
ch := make(chan int, 2) // 缓冲容量为2
ch <- 1
ch <- 2
fmt.Println("Sent 2 values to ch")
// 第三个发送会阻塞,直到有一个值被接收
ch <- 3 // 阻塞在这里
fmt.Println("This line will NOT print unless another goroutine receives from ch")
}
✅ 2. 缓冲通道已空,接收操作阻塞
func main() {
ch := make(chan int, 2)
// 立即进行接收会阻塞,因为通道为空
val := <-ch // 阻塞在这里
fmt.Println("Received:", val)
}
✅ 3. 优化:缓冲通道为空时刚好有接收者,发送数据直接传递给接收者(绕过缓冲区)
func main() {
ch := make(chan int, 2)
go func() {
val := <-ch
fmt.Println("Receiver got:", val)
}()
time.Sleep(time.Millisecond * 100) // 确保接收方先阻塞
ch <- 10 // 此时会直接将 10 传给接收方,不放入缓冲区
fmt.Println("Sender sent 10")
}
✅ 非缓冲通道(Unbuffered Channel)
- 本质是同步机制:收发双方必须同时准备好,才能进行一次传输。
- 没有缓冲区,数据是直接从发送方拷贝到接收方。
✅ 非缓冲通道,收发需同步配对
func main() {
ch := make(chan int)
go func() {
ch <- 42 // 发送操作会阻塞,直到有接收方
fmt.Println("Sent 42")
}()
val := <-ch // 接收操作开始执行后,发送方才被唤醒
fmt.Println("Received:", val)
}
输出:
Received: 42
Sent 42
⚠️ 错误使用通道的阻塞场景
-
未初始化的通道(nil channel) :
-
发送和接收操作会永久阻塞。
-
nil通道通常是由于var ch chan int声明后未调用make初始化。var ch chan int // ch == nil ch <- 1 // 永久阻塞
-
❌ nil 通道,发送和接收都会永久阻塞
func main() {
var ch chan int // 没有初始化,ch == nil
ch <- 1 // 永久阻塞
fmt.Println("Will never be printed")
}
或者:
func main() {
var ch chan int // ch 为 nil
val := <-ch // 永久阻塞
fmt.Println("Received:", val)
}
引发 panic 的情况
-
发送到已关闭的通道:
- 如果尝试向已经关闭的通道发送数据,会引发
panic。
- 如果尝试向已经关闭的通道发送数据,会引发
-
关闭已经关闭的通道:
- 如果尝试关闭一个已经关闭的通道,会引发
panic。
- 如果尝试关闭一个已经关闭的通道,会引发
✅ 示例代码:发送到已关闭的通道
package main
import "fmt"
func main() {
ch := make(chan int)
// 发送数据的 goroutine
go func() {
ch <- 42 // 发送数据
close(ch) // 关闭通道
}()
// 接收数据的 goroutine
go func() {
data := <-ch // 从通道接收数据
fmt.Println("Received:", data)
}()
// 主 goroutine 尝试发送到已关闭的通道
ch <- 100 // 这里会引发 panic
}
解释:
- 在这个例子中,
ch <- 100会引发panic,因为通道ch在之前已经关闭了。关闭后的通道不能再进行发送操作。
✅ 示例代码:关闭已经关闭的通道
package main
import "fmt"
func main() {
ch := make(chan int)
// 关闭通道的 goroutine
go func() {
close(ch)
close(ch) // 再次关闭通道会引发 panic
}()
// 其他 goroutine
go func() {
data := <-ch
fmt.Println("Received:", data)
}()
}
解释:
- 在上面的例子中,尝试两次关闭通道
ch。第一次关闭是合法的,但第二次关闭时会引发panic,因为通道已经被关闭。
Note
如果通道关闭且没有剩余数据,接收操作会返回该类型的零值,而不是引发 panic。
package main
import "fmt"
func main() {
ch := make(chan int)
// 发送数据的 goroutine
go func() {
ch <- 42
close(ch) // 关闭通道
}()
// 接收数据的 goroutine
go func() {
data, ok := <-ch
fmt.Println("Received:", data, "Channel open:", ok)
data, ok = <-ch // 通道已经关闭,再次接收
fmt.Println("Received:", data, "Channel open:", ok)
}()
// 主 goroutine 等待
fmt.Scanln()
}
输出解释:
Received: 42 Channel open: true
Received: 0 Channel open: false