Search code examples
goconcurrencychannelwaitgroup

Golang channel writing to and reading from issue - new to golang channels


This is plain producer consumer problem with golang channels. I want to put something in channel, and out to write it on console.

package main

import (
    "fmt"
    "sync"
    "time"
)
func main() {
    chIn := make(chan string)

    var wg sync.WaitGroup

    wg.Add(2)

    go func() {
        defer wg.Done()
        for i := 0; i < 3; i++ {
            chIn <- "in"
            fmt.Println("in")
        }
        close(chIn)
    }()

    go func() {
        defer wg.Done()
        for range chIn {
            <-chIn
            fmt.Println("out")
        }
    }()

    wg.Wait()
}
  1. First problem is that is not sorted, I would expect in, out x3 but it's not. There is multiple 'in' and 'out' grouped. But I have looked at other problems, and it seems it's just not printing in correct order. Please correct me if I'm wrong
  2. Second is that there is only two 'out's and it should of been 3 of them.

Solution

  • Dealing with your questions in reverse order...

    As Burak Serdar mentions in their comment, the difference in the number of outputs vs the number of inputs is caused by the fact that you have an additional, unnecessary read from the channel in the second goroutine:

    This:

       for range chIn {
           <-chIn
           fmt.Println("out")
       }
    

    Should be something more like:

       for in := range chIn {
           fmt.Println(in)
       }
    

    This will deal with the different number of outputs vs inputs.

    To understand the problems with "sorting", it might help to send values that help identify the order in which they were sent, and to print those values so that you can see the order in which they are received.

    So, for sending:

        chIn <- fmt.Sprintf("#%d", i+1)
        fmt.Printf("sent #%d\n", i+1)
    
    

    The change suggested above to the second routine already takes care of outputting the received value but you may still be confused by the output. You might for example see a value (seemingly) being received before it has even been sent!

    There are two reasons for this:

    1. you set up the sending goroutine before establishing the receiving go routine

    2. you emit a "sent" log after sending, which creates a window of opportunity for the message to be received and processed before the sending goroutine has had a chance to emit that log

    The solution is to address these sequencing problems:

    1. establish your receiver goroutine first - i.e. don't start sending messages to the channel until you have started the goroutine to start reading from it (this isn't always necessary when dealing with channels, but may help you understand what is going on in this case)

    2. Emit a "sending" message to the console before sending a message to the channel; this eliminates the possibility of the sent message being received and processed before the logged output is emitted

    This should result in something similar to this:

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        chIn := make(chan string)
    
        var wg sync.WaitGroup
    
        wg.Add(2)
    
        go func() {
            defer wg.Done()
            for in := range chIn {
                fmt.Printf("rcvd: %s\n", in)
            }
        }()
        
        go func() {
            defer wg.Done()
            for i := 0; i < 3; i++ {
                fmt.Printf("sending #%d\n", i+1)
                chIn <- fmt.Sprintf("#%d", i+1)
            }
            close(chIn)
        }()
    
        wg.Wait()
    }
    

    The output will still consist of interleaved send and receive logs, but the sequencing should now be sensible, yielding something similar to:

    sending #1
    sending #2
    rcvd: #1
    rcvd: #2
    sending #3
    rcvd: #3
    

    A runnable solution may be found in this Playground