Search code examples
gochannelgoroutine

Go channel not receiving/printing last value sent to channel


Here's a piece of code which outputs a list of integers as pushed to a channel. Else a select checks and prints necessary timeout message.

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func main() {
    wg.Add(1)
    c := make(chan int)

    go readFromChannel(c, time.After(time.Duration(2)*time.Second))
//  time.Sleep(time.Duration(5) * time.Second) //Talking about uncommenting this line
    c <- 10
    c <- 20
    c <- 30
    c <- 40
    c <- 50
    wg.Wait()
}

func readFromChannel(c chan int, ti <-chan time.Time) {
    defer wg.Done()
    go func() {
        for {
            select {
            case x := <-c:
                fmt.Println("Read", x)
            case t :=<-ti:
                fmt.Println("TIMED OUT with "+t.String())
            }
        }
    }()

}

Also here's the playground link for the same : https://play.golang.org/p/4hNWze4Pfwr This code outputs list of integer like

Read 10
Read 20
Read 30
Read 40
Read 50

But when I un-comment the line that make the main routine go to sleep for 5 sec(mentioned in the code as comment/line number 16), output changes to :

TIMED OUT with 2009-11-10 23:00:02 +0000 UTC m=+2.000000001
Read 10
Read 20
Read 30
Read 40

I want to understand why the last Read 50 is not being printed in second case.


Solution

  • The problem is that your wg.Done() is in the wrong place. It must be in your goroutine, but you're executing it before the goroutine even starts, so your program is liable to exit before doing any work.

    Change this:

    defer wg.Done()
    go func() {
    

    to this:

    go func() {
        defer wg.Done()
    

    Of course then you'll have an infinitely running goroutine, because your for loop has no exit condition. You'll need to add one, probably by checking for the channel closing:

            select {
            case x, ok := <-c:
                if !ok { // channel was closed
                    return
                }
                fmt.Println("Read", x)
            case t :=<-ti:
                fmt.Println("TIMED OUT with "+t.String())
            }
    

    Then tell your main go routine to close the channel when it's done:

    c <- 40
    c <- 50
    close(c)  // We're done, so tell the goroutine to finish up
    wg.Wait() // But wait until it's done