How can I use goroutine to execute for loop?

Issue

Suppose I have a slice like:

stu = [{"id":"001","name":"A"} {"id":"002", "name":"B"}] and maybe more elements like this. inside of the slice is a long string, I want to use json.unmarshal to parse it.

type Student struct {
   Id   string `json:"id"`
   Name string `json:"name"`
}

studentList := make([]Student,len(stu))
for i, st := range stu {
   go func(st string){
      studentList[i], err = getList(st)
      if err != nil {
         return ... //just example
      }
   }(st)
}
//and a function like this
func getList(stu string)(res Student, error){
   var student Student
   err := json.Unmarshal(([]byte)(stu), &student)
   if err != nil {
      return
   }
   return &student,nil
}

I got the nil result, so I would say the goroutine is out-of-order to execute, so I don’t know if it can use studentList[i] to get value.

Solution

Here are a few potential issues with your code:

Value of i is probably not what you expect

for i, st := range stu {
   go func(st string){
      studentList[i], err = getList(st)
      if err != nil {
         return ... //just example
      }
   }(st)
}

You kick off a number of goroutines and, within them, reference i. The issue is that i is likely to have changed between the time you started the goroutine and the time the goroutine references it (the for loop runs concurrently to the goroutines it starts). It is quite possible that the for completes before any of the goroutines do meaning that all output will be stored in the last element of studentList (they will overwrite each other so you will end up with one value).

A simple solution is to pass i into the goroutine function (e.g. go func(st string, i int){}(st, i) (this creates a copy). See this for more info.

Output of studentList

You don’t say in the question but I suspect you are running fmt.Println(studentList[1] (or similar) immediately after the for loop completes. As mentioned above it’s quite possible that none of the goroutines have completed at that point (or they may of, you don’t know). Using a WaitGroup is a fairly easy way around this:

var wg sync.WaitGroup
wg.Add(len(stu))
for i, st := range stu {
    go func(st string, i int) {
        var err error
        studentList[i], err = getList(st)
        if err != nil {
            panic(err)
        }
        wg.Done()
    }(st, i)
}
wg.Wait()

I have corrected these issues in the playground.

Answered By – Brits

Answer Checked By – Cary Denson (GoLangFix Admin)

Leave a Reply

Your email address will not be published.