当我在发件人一侧关闭时,为什么仍会慌张地“在关闭的通道上发送”?

I have a stopChan to notify senders to close channel and a sync.Once to make sure only one sender can close the channel, but I still get a "send on closed channel" panic, why?

func muitiSenderClose() {
    const SenderNum = 3

    wg := sync.WaitGroup{}
    wg.Add(SenderNum)

    intChan := make(chan int)

    stopChan := make(chan struct{})

    once := sync.Once{}
    for i := 0; i < SenderNum; i++ {
        go func(i int) {
            defer wg.Done()
            needStop := false
            for {
                select {
                case <-stopChan:
                    needStop = true
                case intChan <- 1:
                case intChan <- 2:
                case intChan <- 3:
                }

                if needStop {
                    break
                }
            }
            once.Do(func() {
                fmt.Printf("%d want to close chan
",i)
                close(intChan)
            })
            fmt.Println("End. [sender] %id", i)
        }(i)
    }
    sum := 0
    for e := range intChan {
        fmt.Printf("Receive %d
", e)
        sum += e
        if sum > 10 {
            close(stopChan)
            fmt.Printf("Got %d
", sum)
            break
        }
    }
    fmt.Println("End. [receiver]")

    wg.Wait()
}

go concurrency is very powerful. Coordinating concurrency is very hard. Fortunately the go standard library has many tools to help with this. You should probably familiarize yourself with the context package.

context.Context use done-channels under the covers (similar to your stopChan) but have other mechanisms like cancelation chaining. They are also used throughout the go standard library for http, database and other blocking-type requests.

As @JimB has mentioned, usually sync.Once are rarely needed when coordinating producer/consumers. chans and sync.WaitGroups are usually enough.

Anyway, here's the coordination fix to your code using context.Context:

https://play.golang.org/p/QwszE_bW41X