Home > Enterprise >  GoLang concurrency- Main routine is never called
GoLang concurrency- Main routine is never called

Time:04-29

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...

CodePudding user response:

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)
}

CodePudding user response:

Your channel is unbuffered and so, your code in main.go will be infinitely running, waiting for something to happen, because the code cannot continue since unbuffered channels are synchronous, where anyone who is listening on that channel can only stop listen when it receives a value (in this case, as it can sometimes take time for the request to arrive or never arrive, the code will be infinitely running inside main.go).

You have two options:

  • Or you define buffers for your channel to be the size of your list of URL's and make your code really asynchronous and concurrent.

        op := make(chan models.URLCheckerModel, len(links))
    
  • Or you will have to create several channels, one for each URL, so that when each one is processed, the value is loaded and you CLOSE your channel within the search method.

     links := []string{"http://google.com", "http://golang.org", "http://amazon.com"}
    
     for i := 0; i < len(links); i   {
        // call to goroutine
        op := make(chan models.URLCheckerModel)
    
        go func(str string) {
           printChannelData(str, op)
    
           fmt.Println(<-op)
        }(links[i])
    }
    
    
    
    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,
         }
     }
    
        close(url)
     }
    
  • Related