Komu
Komu

Reputation: 15018

Golang program hangs without finishing execution

I have the following golang program;

package main

import (
    "fmt"
    "net/http"
    "time"
)

var urls = []string{
    "http://www.google.com/",
    "http://golang.org/",
    "http://yahoo.com/",
}

type HttpResponse struct {
    url      string
    response *http.Response
    err      error
    status   string
}

func asyncHttpGets(url string, ch chan *HttpResponse) {
    client := http.Client{}
    if url == "http://www.google.com/" {
        time.Sleep(500 * time.Millisecond) //google is down
    }

    fmt.Printf("Fetching %s \n", url)
    resp, err := client.Get(url)
    u := &HttpResponse{url, resp, err, "fetched"}
    ch <- u
    fmt.Println("sent to chan")
}

func main() {
    fmt.Println("start")
    ch := make(chan *HttpResponse, len(urls))
    for _, url := range urls {
        go asyncHttpGets(url, ch)
    }

    for i := range ch {
        fmt.Println(i)
    }
    fmt.Println("Im done")

}

Run it on Playground

However when I run it; it hangs (ie the last part that ought to print Im done doesnt run.) Here's the terminal output;;
$ go run get.go
start
Fetching http://yahoo.com/
Fetching http://golang.org/
Fetching http://www.google.com/
sent to chan
&{http://www.google.com/ 0xc820144120 fetched}
sent to chan
&{http://golang.org/ 0xc82008b710 fetched}
sent to chan
&{http://yahoo.com/ 0xc82008b7a0 fetched}

Upvotes: 2

Views: 4984

Answers (2)

Datsik
Datsik

Reputation: 14824

Another good dirty devious trick would be to use sync.WaitGroup and increment it per goroutine and then monitor it with a Wait and after its done it will close your channel allowing the next blocks of code to run, the reason I am offering you this approach is because it gets away from using a static number in a loop like len(urls) so that you can have a dynamic slice that might change and what not.

The reason Wait and close are in their own goroutine is so that your code can reach the for loop to range over your channel

package main

import (
    "fmt"
    "net/http"
    "time"
    "sync"
)

var urls = []string{
    "http://www.google.com/",
    "http://golang.org/",
    "http://yahoo.com/",
}

type HttpResponse struct {
    url      string
    response *http.Response
    err      error
    status   string
}

func asyncHttpGets(url string, ch chan *HttpResponse, wg *sync.WaitGroup) {
    client := http.Client{}
    if url == "http://www.google.com/" {
        time.Sleep(500 * time.Millisecond) //google is down
    }

    fmt.Printf("Fetching %s \n", url)
    resp, err := client.Get(url)
    u := &HttpResponse{url, resp, err, "fetched"}
    ch <- u
    fmt.Println("sent to chan")
    wg.Done()
}

func main() {
    fmt.Println("start")
    ch := make(chan *HttpResponse, len(urls))
    var wg sync.WaitGroup
    for _, url := range urls {
        wg.Add(1)
        go asyncHttpGets(url, ch, &wg)
    }

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

    for i := range ch {
        fmt.Println(i)
    }
    fmt.Println("Im done")

}

Upvotes: 0

joshlf
joshlf

Reputation: 23567

The problem is that ranging over a channel in a for loop will continue forever unless the channel is closed. If you want to read precisely len(urls) values from the channel, you should loop that many times:

for i := 0; i < len(urls); i++ {
    fmt.Println(<-ch)
}

Upvotes: 5

Related Questions