golang - 阻塞场景

一、阻塞的核心场景

  1. 无缓冲通道(Unbuffered Channel)

    • 发送操作会阻塞,直到另一个goroutine准备好接收。

    • 接收操作会阻塞,直到另一个goroutine准备好发送。

  2. 带缓冲通道(Buffered Channel)

    • 发送操作仅在缓冲区满时阻塞。

    • 接收操作仅在缓冲区空时阻塞。

  3. 其他场景

    • time.Sleephttp.Get、文件读写等I/O操作。

    • sync.WaitGroupsync.Mutex等同步工具。


二、代码示例

示例1:无缓冲通道的阻塞

package main

import "fmt"

func main() {
    ch := make(chan int) // 无缓冲通道
    go func() {
        fmt.Println("准备发送数据: 42")
        ch <- 42      // 发送数据,会阻塞直到主goroutine接收
        fmt.Println("数据已发送")
    }()
    fmt.Println("准备接收数据")
    fmt.Println("收到数据:", <-ch) // 接收数据,解除子goroutine的阻塞
    // 等待用户输入,防止主goroutine退出
    fmt.Scanln()
}

输出:

准备接收数据
准备发送数据: 42
收到数据: 42
数据已发送

解释:

  • 子goroutine在发送ch <- 42时阻塞,直到主goroutine执行<-ch

  • 主goroutine的fmt.Scanln()是为了防止程序提前退出。


示例2:带缓冲通道的阻塞

package main

import "fmt"

func main() {
    ch := make(chan int, 2) // 缓冲区大小为2
    ch <- 1                 // 不阻塞(缓冲区未满)
    ch <- 2                 // 不阻塞(缓冲区未满)
    fmt.Println("发送了1和2")

    go func() {
        ch <- 3 // 阻塞(缓冲区已满),直到主goroutine接收数据
        fmt.Println("发送了3")
    }()

    fmt.Println("接收:", <-ch) // 接收1,腾出一个缓冲区位置
    fmt.Println("接收:", <-ch) // 接收2
    fmt.Println("接收:", <-ch) // 接收3,解除子goroutine的阻塞
}

输出:

发送了1和2
接收: 1
接收: 2
接收: 3
发送了3

示例3:用select处理阻塞

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)
    quit := make(chan bool)

    go func() {
        for {
            select {
            case num := <-ch:
                fmt.Println("收到:", num)
            case <-quit:
                fmt.Println("退出goroutine")
                return
            default:
                fmt.Println("等待数据...")
                time.Sleep(500 * time.Millisecond)
            }
        }
    }()

    ch <- 1
    ch <- 2
    quit <- true
    time.Sleep(1 * time.Second) // 等待子goroutine退出
}

 

输出:

等待数据...
收到: 1
收到: 2
退出goroutine

解释:

  • selectdefault分支用于实现非阻塞操作。

  • 如果没有default分支,select会阻塞直到某个case就绪。


三、典型阻塞问题:死锁

如果所有goroutine都被阻塞,程序会触发死锁(panic):

func main() {
    ch := make(chan int)
    ch <- 42       // 发送阻塞(没有接收方)
    fmt.Println(<-ch)
}

运行结果:

fatal error: all goroutines are asleep - deadlock!

四、总结

  1. 阻塞是Go并发模型的核心机制,通过通道的阻塞/非阻塞行为协调goroutine。

  2. 避免死锁:确保至少有一个goroutine能继续执行。

  3. 工具:使用select、带缓冲通道、sync.WaitGroup等处理阻塞逻辑。

posted @ 2025-05-28 10:11  apeNote  阅读(24)  评论(0)    收藏  举报