Skip to content

阻塞

在 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.Lockmutex.Unlock

  • sync.MutexLock() 会阻塞,直到其他 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.CondWait() 会阻塞,直到被 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.ListenAndServehttp.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.WaitGroupselect 等)可以避免不必要的阻塞,提升程序的并发性和性能。