阻塞
在 Go 中,以下几种情况可能会导致 阻塞,即当前 goroutine 停止执行,直到某个条件满足:
1. 通道 (Channel) 操作
- 发送操作(
chan <- value)会阻塞,直到另一个 goroutine 从该通道接收数据。 - 接收操作(
value := <-chan)会阻塞,直到有数据被发送到该通道。
go
ch := make(chan int)
// 接收操作将阻塞,直到 ch 中有数据
go func() {
fmt.Println(<-ch) // 会阻塞
}()
// 发送操作将阻塞,直到接收操作准备好接收数据
ch <- 42 // 会阻塞解决方案:可以使用缓冲通道(make(chan type, capacity))来避免阻塞,或者使用 select 来进行多路复用。
2. sync.WaitGroup
WaitGroup.Wait()会阻塞,直到所有的 goroutine 都调用Done(),即Add设置的计数值减少到 0。
go
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Goroutine finished")
}()
wg.Wait() // 阻塞直到 goroutine 完成3. time.Sleep
time.Sleep(d)会阻塞当前 goroutine 指定的时间d。
go
fmt.Println("Start sleeping")
time.Sleep(2 * time.Second) // 阻塞 2 秒
fmt.Println("Finished sleeping")4. select 语句
select语句会阻塞,直到某个 case 能够执行。如果所有 case 都没有准备好,select会一直阻塞。
go
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(2 * time.Second)
ch1 <- 1 // 向 ch1 发送数据
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- 2 // 向 ch2 发送数据
}()
// 阻塞直到 ch1 或 ch2 有数据可以接收
select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
}注意:如果 select 中的所有 case 都阻塞(没有可用的通道),那么 select 本身就会一直阻塞。
5. os.Stdin 或 文件操作
- 如果从标准输入(
os.Stdin)或文件中读取数据时,且没有数据可读取,这些操作会阻塞。
go
var input string
fmt.Scanln(&input) // 阻塞直到有输入6. mutex.Lock 和 mutex.Unlock
sync.Mutex的Lock()会阻塞,直到其他 goroutine 调用Unlock()释放锁。如果多个 goroutine 同时请求一个锁,只有第一个获得锁的 goroutine 会继续执行,其余的 goroutine 将会阻塞,直到锁被释放。
go
var mu sync.Mutex
go func() {
mu.Lock() // 阻塞直到成功获得锁
fmt.Println("Locked!")
mu.Unlock()
}()
go func() {
mu.Lock() // 阻塞直到第一个 goroutine 解锁
fmt.Println("Locked again!")
mu.Unlock()
}()7. sync.Cond
sync.Cond的Wait()会阻塞,直到被Signal()或Broadcast()唤醒。
go
var mu sync.Mutex
cond := sync.NewCond(&mu)
go func() {
mu.Lock()
cond.Wait() // 阻塞直到接收到信号
fmt.Println("Received signal")
mu.Unlock()
}()
go func() {
mu.Lock()
cond.Signal() // 唤醒一个等待中的 goroutine
mu.Unlock()
}()8. http.ListenAndServe 或 http.Serve
- 当你调用
http.ListenAndServe()或http.Serve()启动 HTTP 服务时,它会阻塞当前 goroutine,直到服务器停止。
go
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, world!")
})
http.ListenAndServe(":8080", nil) // 阻塞直到服务器停止总结:
阻塞的原因通常是等待某些资源或事件的发生。常见的阻塞操作包括通道的发送和接收、等待其他 goroutine 完成、等待时间的到期、以及文件或网络操作等。通过合理的并发控制(如使用缓冲通道、sync.WaitGroup、select 等)可以避免不必要的阻塞,提升程序的并发性和性能。