Channel returning same value in loop

Issue

I’m experimenting with the concurrency in golang. I had written a code that calls URL and extract content type from Header. I had approached in two ways. Approach 1 works and Approach 2 is not producing expected result. I want know a way to make my approach 2 work.

Approach 1
Here I sent string to channel ch from within ctypes.

package main

import (
    "fmt"
    "net/http"
)

func ctypes(url string, ch chan string) {
    resq, err := http.Get(url)

    if err != nil {
        ch <- err.Error()
        return
    }

    defer resq.Body.Close()

    ct := resq.Header.Get("Content-type")

    if ct == "" {
        ch <- fmt.Sprintf("No such field in %v", url)
        return
    }
    ch <- fmt.Sprintf("URL: %v; Content Type: %v", url, ct)
    return
}

func main() {
    var sites []string

    ch := make(chan string)

    sites = []string{
        "https://golang.org",
        "https://api.github.com",
        "https://youtube.com",
    }

    for _, url := range sites {
        go ctypes(url, ch)
    }

    for out := range sites {
        fmt.Println(out)
    }
}

OUTPUT 1: Producing desired results.

go run sites_with_channel.go 
URL: https://api.github.com; Content Type: application/json; charset=utf-8
URL: https://golang.org; Content Type: text/html; charset=utf-8
URL: https://youtube.com; Content Type: text/html; charset=utf-8

Approach 2
Created anonymous function in a for loop inside a main to send string into a channel

package main

import (
    "fmt"
    "net/http"
)

func ctypes(url string) string {
    resq, err := http.Get(url)

    if err != nil {
        return err.Error()
    }

    defer resq.Body.Close()

    ct := resq.Header.Get("Content-type")

    if ct == "" {
        return fmt.Errorf("No such field in %v", url).Error()
    }
    return fmt.Sprintf("URL: %v; Content Type: %v\n", url, ct)
}

func main() {
    var sites []string

    ch := make(chan string)

    sites = []string{
        "https://golang.org",
        "https://api.github.com",
        "https://youtube.com",
    }

    for _, url := range sites {
        go func() {
            ch <- ctypes(url)
        }()
    }

    for range sites {
        out := <-ch
        fmt.Println(out)
    }

    defer close(ch)

}

Output 2

go run cont.go 
URL: https://youtube.com; Content Type: text/html; charset=utf-8
URL: https://youtube.com; Content Type: text/html; charset=utf-8
URL: https://youtube.com; Content Type: text/html; charset=utf-8

Solution

The loop variable is rewritten in for loops, so when the goroutine reads url, it is probably rewritten already. This is a common error. You should use a copy of the loop-variable in the goroutine.

for _, url := range sites {
        go func(u string) {
            ch <- ctypes(u)
        }(url)
    }

Answered By – Burak Serdar

Answer Checked By – Katrina (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.