Search code examples
goconcurrencygoroutinechannel

GoLang concurrency- Main routine is never called


I am a newbie to golang. Trying to learn concurrency. The concept with the join point after forking using the channels troubles me now. THis is the code that I have in place

package main

import (
    "fmt"
    "net/http"

    "github.com/vijay-psg587/golang/golang-srv/src/modules/test-dir-DONT-USE/concurrency/status-checker/models"
)

func printChannelData(str string, url chan models.URLCheckerModel) {
    
    fmt.Println("Data in channel is: ", str)
    resp, err := http.Get(str)
    if err != nil {

        url <- models.URLCheckerModel{
            StatusCode: 500,
            URL:        str,
            Message:    err.Error(),
        }

    } else {
        

        url <- models.URLCheckerModel{
            StatusCode: 200,
            URL:        str,
            Message:    resp.Status,
        }
    }

}

func main() {
    fmt.Println("Main started...")
    

    links := []string{"http://google.com", "http://golang.org", "http://amazon.com"}

    op := make(chan models.URLCheckerModel)
    defer close(op)
    for i := 0; i < len(links); i++ {
        // call to goroutine

        go func(str string) {
            printChannelData(str, op)
        }(links[i])

        

    }

    for data := range op {

        fmt.Println("data receveied:", data)
    }

    fmt.Println("Main ended...")
}

Now when I execute this, my go function does NOT end. It gets the data

Main started...
Data in channel is:  http://amazon.com
Data in channel is:  http://google.com
Data in channel is:  http://golang.org

data receveied: {200 http://google.com 200 OK}

data receveied: {200 http://golang.org 200 OK}

data receveied: {200 http://amazon.com 200 OK}

Three go routines kick off and run, but it never ends. Doesn't come back to the main go routine at all

Now if I make the change like this, (in the join point)

// for data := range op {

    //  fmt.Println("data receveied:", data)
    // }
    fmt.Println("data received:", <-op)
    fmt.Println("data received:", <-op)
    fmt.Println("data received:", <-op)

instead of the for loop, then I get the data and the main go routine also ends. Not sure where I am making the mistake and why the main routine is never called

Main started...
Data in channel is:  http://amazon.com
Data in channel is:  http://golang.org
Data in channel is:  http://google.com
data received: {200 http://google.com 200 OK}
data received: {200 http://golang.org 200 OK}
data received: {200 http://amazon.com 200 OK}
Main ended...

Solution

  • When using for range over a channel in Go, the iterator will continue to block until the underlying channel is closed.

    x := make(chan int, 2)
    x <- 1
    x <- 2
    close(x) // Without this, the following loop will never stop
    
    for i := range x {
      print(i)
    }
    

    In your case, using defer means that the channel will only be closed once the parent method exits (i.e. after the loop finishes), which leads to a deadlock.

    Usually, when using a fan-out/fan-in model, you'll use something like the sync.WaitGroup to coordinate closing the channel/continuing.

    In your case, that would likely look something like the following:

    links := []string{"http://google.com", "http://golang.org", "http://amazon.com"}
    
    op := make(chan models.URLCheckerModel)
    var wg sync.WaitGroup
    
    for i := 0; i < len(links); i++ {
        wg.Add(1)
        go func(str string) {
            defer wg.Done()
            printChannelData(str, op)
        }(links[i])
    }
    
    go func() {
      wg.Wait()
      close(op)
    }()
    
    for data := range op {
        fmt.Println("data receveied:", data)
    }