Search code examples
gogoroutine

Are creating go routines asynchrnous?


I'm trying to fetch the content of an API with numerous goroutines. I'm using a for loop to iterate over different character, but it seems like the forloop reaches its final value, before the requests are sent off.

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
)

type people struct {
    Name string `json:"name"`
}

func main(){

    names := make(chan string, 25)
    var wg sync.WaitGroup
    for i := 0; i < 25; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            var p people
            url := fmt.Sprintf("https://swapi.dev/api/people/%d", i)
            getJSON(url, &p)
            names <- p.Name
        }()
    }
    name := <-names
    fmt.Println(name)
    wg.Wait()

}

func getJSON(url string, target interface{}) error {
    r, err := http.Get(url)
    if err != nil {
        return err
    }
    defer r.Body.Close()
    json.NewDecoder(r.Body).Decode(target)
    return nil
}

Also, if somebody could improve my code quality, I'd be very grateful, I'm very new to Golang and don't have anybody to learn from!


Solution

  • You go routines are all using the same variable i. So on the first loop, you launch a goroutine that makes a url from i, and on the next loop i is incremented before that routine has a chance to run.

    It's a common mistake in GoLang. The solution is to make a variable for each loop, and pass that one forward. You can either do it with a closure like this (playground).

        for i := 0; i < 25; i++ {
            wg.Add(1)
            localI := i
            go func() {
                defer wg.Done()
                var p people
                // Use LocalI here
                url := fmt.Sprintf("https://swapi.dev/api/people/%d", localI)
                getJSON(url, &p)
                names <- p.Name
            }()
        }
    

    Or as an argument to the function (playground)

        for i := 0; i < 25; i++ {
            wg.Add(1)
            localI := i
            go func(localI int) {
                defer wg.Done()
                var p people
                // Use LocalI here
                url := fmt.Sprintf("https://swapi.dev/api/people/%d", localI)
                getJSON(url, &p)
                names <- p.Name
             // Pass i here. Since I is a primitive, it is passed by value, not reference.
             // Meaning a copy is made.
            }(i) 
        }
    

    Here is a good writeup on the mistake you made: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables

    And the one above it is good to read too!