Search code examples
gochannel

Problem with go channels, send on closed channel


I'm receiving a fatal error: "send on closed channel", sometimes that I run this code, I have tried multiple solutions but none of them worked, this is a representation of the code, easy to understand and easy to test:

package main

import (
    "fmt"
    "sync"
)

func main() {

    var wg sync.WaitGroup
    outputCh := make(chan *string)
    stopCh := make(chan struct{})
    wgCh := make(chan bool)

    for i := 0; i < 20; i++ {
        wg.Add(1)
        go request(&outputCh, &stopCh, &wg)
    }

    go func() {
        wg.Wait()
        close(wgCh)
    }()

    select {

    // All goroutines finished
    case <-wgCh:
        close(outputCh)

    // First goroutine finished correctly
    case output := <-outputCh:
        close(stopCh)
        fmt.Println("closing chanel")
        close(outputCh)
        fmt.Println(*output)
    }
}

func request(outputCh *chan *string, stopChan *chan struct{}, wg *sync.WaitGroup) {
    defer wg.Done()

    testStr := "test"
    select {
    case <-*stopChan:
        fmt.Println("channels is closed")
    default:
        fmt.Println("channels send")
        *outputCh <- &testStr
    }
}

What I want is launch the request function n times and get the first request that finish, then close the channel and do not send more to the channel, if none of the request finish succesfully, then wait for all the goroutines to finish.

I imagine that this happens because two or more gouroutines checks if the channel is closed at the same time and the two try to write in the channel, and this causes the fatal error.

error:

goroutine 19 [running]:
main.request(0xc00000a028, 0xc00000a030, 0x0?)
        C:/test/main.go:49 +0x135
created by main.main
        C:/test/main.go:17 +0xd3
panic: send on closed channel

Can anyone clarify why is this happening?

Thanks in advance


Solution

  • The problem is that the receiving goroutine (main) closes the outputCh too early. Some other goroutine is still able to try sending on it.

    Here's another way to do it:

    package main
    
    import (
        "fmt"
        "math/rand"
        "sync"
        "time"
    )
    
    func main() {
        var wg sync.WaitGroup
        output := make(chan string)
        stop := make(chan bool)
        done := make(chan bool)
    
        for i := 0; i < 20; i++ {
            wg.Add(1)
            go request(output, stop, &wg)
        }
    
        go func() {
            wg.Wait()
            done <- true
        }()
    
        firstOutput := <-output
        fmt.Println("output:", firstOutput)
    
        fmt.Println("closing the stop channel")
        close(stop)
    
        <-done
        fmt.Println("end of main")
    }
    
    func request(output chan string, stop chan bool, wg *sync.WaitGroup) {
        defer wg.Done()
    
        fmt.Println("request started")
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
    
        select {
        case output <- "test":
            fmt.Println("output sent")
        case <-stop:
            fmt.Println("stop channel is closed")
        }
    }