将通道从无缓冲更改为缓冲可防止 goroutine 运行

Changing channel from unbuffered to buffered prevents goroutine from running

这是一个在 goroutine 中使用通道和 select 的练习。如果断开通道更改为缓冲通道,则 goroutine 根本不会 运行。

为什么从无缓冲通道更改为缓冲通道会阻止 运行 启用 goroutine?

func SelectDemo(wg *sync.WaitGroup) {

    messageCh := make(chan int, 10)
    disconnectCh := make(chan struct{})
    //  go routine won't run if channel is buffered
    //disconnectCh := make(chan struct{}, 1)

    defer close(messageCh)
    defer close(disconnectCh)
    go func() {
        fmt.Println("  goroutine")
        wg.Add(1)
        for {
            select {
            case v := <-messageCh:
                fmt.Println(v)
            case <-disconnectCh:
                fmt.Println("  disconnectCh")
                //  empty the buffered channel before exiting
                for {
                    select {
                    case v := <-messageCh:
                        fmt.Println(v)
                    default:
                        fmt.Println("  disconnection, return")
                        wg.Done()
                        return
                    }
                }
            }
        }
    }()

    fmt.Println("Sending ints")
    for i := 0; i < 10; i++ {
        messageCh <- i
    }

    fmt.Println("Sending done")
    disconnectCh <- struct{}{}
}

这是从 main 调用函数的代码。我使用等待组来确保 goroutine 在程序退出之前完成:

wg := sync.WaitGroup{}
ch09.SelectDemo(&wg)
wg.Wait()

程序执行速度快

访问URL:https://pkg.go.dev/sync#WaitGroup.Add

请注意,当计数器为零时发生的具有正增量的调用必须发生在等待之前。具有负增量的调用或在计数器大于零时开始的具有正增量的调用可能随时发生。 通常这意味着对 Add 的调用应该在创建 goroutine 或其他要等待的事件的语句之前执行。 如果重用 WaitGroup 来等待多个独立的事件集,则新的 Add 调用必须在所有先前的 Wait 调用返回后发生。请参阅 WaitGroup 示例。

func SelectDemo(wg *sync.WaitGroup) {

    messageCh := make(chan int, 10)
    disconnectCh := make(chan struct{}, 1)
    //  go routine won't run if channel is buffered
    //disconnectCh := make(chan struct{}, 1)

    wg.Add(1)

    defer close(messageCh)
    defer close(disconnectCh)
    go func() {
        fmt.Println("  goroutine")
        for {
            select {
            case v := <-messageCh:
                fmt.Println(v)
            case <-disconnectCh:
                fmt.Println("  disconnectCh")
                //  empty the buffered channel before exiting

                fmt.Println("  disconnection, return")
                wg.Done()
                return
            }
        }
    }()

    fmt.Println("Sending ints")
    for i := 0; i < 10; i++ {
        messageCh <- i
    }

    //Delay sending exit signal
    time.Sleep(3 * time.Second)

    fmt.Println("Sending done")
    disconnectCh <- struct{}{}
}

我修改了你的代码

再试一次!!!

该代码逻辑有很多缺陷 - 其中一些是:
1- 由于 messageCh 被缓冲,此代码不阻塞:

    for i := 0; i < 10; i++ {
        messageCh <- i
    }

所以下一个代码在 运行 的快速路径中:

disconnectCh <- struct{}{}

如果使 disconnectCh 缓冲,此行 运行 也不会 阻塞 ,并且 SelectDemo 函数 可能会在 运行 之前退出 wg.Add(1)

所以:你必须输入:

wg.Add(1)

之前

go func() {

2- 即使在 go func() { 之前有 wg.Add(1) code -
你有:

    defer close(messageCh)
    defer close(disconnectCh)

这将 关闭 两个通道 SelectDemo 函数 return 这个 select 是一个随机的 selection,因为两个通道都准备好了:

fmt.Println("  goroutine")
        for {
            select {
            case v := <-messageCh:
                fmt.Println(v)
            case <-disconnectCh:

并且 极有可能 第二个 select:

                for {
                    select {
                    case v := <-messageCh:
                        fmt.Println(v)
                    default:
                        fmt.Println("  disconnection, return")
                        wg.Done()
                        return
                    }
                }

将在 messageCh 关闭后永远 运行,returning 0 在通道数据读取后永远

case v := <-messageCh:
    fmt.Println(v)