Detailed explanation of for ES2en and goroutine in Golang

  • 2020-06-07 04:40:13
  • OfStack

background

During my recent study of MIT's distributed course 6.824, I encountered a number of problems implementing the Raft protocol using Go. Share for your reference to learn, the following words do not say much, to see a detailed introduction to it.

See the following code:


for i := 0; i < len(rf.peers); i++ {
  DPrintf("i = %d", i)

  if i == rf.me {
   DPrintf("skipping myself #%d", rf.me)
   continue
  }

  go func() {
   DPrintf("len of rf.peers = %d", len(rf.peers))
   DPrintf("server #%d sending request vote to server %d", rf.me, i)
   reply := &RequestVoteReply{}
   ok := rf.sendRequestVote(i, args, reply)
   if ok && reply.VoteGranted && reply.Term == rf.currentTerm {
    rf.voteCount++
    if rf.voteCount > len(rf.peers)/2 {
     rf.winElectionCh <- true
    }
   }
  }()
}

Where, the length of peers slice is 3, so the highest subscript is 2. In non-parallel programming, the code of ES13en-ES14en should be intuitive, and I didn't realize there was a problem at that time. However, during debugging, 1 reported index out of bounds error directly. The debug information shows that the value of i is 3. At that time, it was not clear that the loop condition was i < How did 2 become 3?

Analysis of the

I don't know what's going on, but I do know that goroutine was introduced in the loop. Through Google, I found that Common ES32en-ES33en goroutines on loop variables variables specifically mentioned this question, it seems that it is very common, laugh and cry ~

Beginners often use the following code to process data in parallel:


for val := range values {
 go val.MyMethod()
}

Or use closures (closure) :


for val := range values {
 go func() {
  fmt.Println(val)
 }()
}

The problem here is that val is actually a single variable that iterates over all the data in the slice. Since the closure is simply bound to this val variable, it is highly likely that the above code will run as all goroutine output the last element of the slice. This is because it is very likely that goroutine will not execute until for-ES53en has finished executing, at which point the value of val points to the last element in the slice.


The val variable in the above loops is actually a single variable that takes on the value of each slice element. Because the closures are all only bound to that one variable, there is a very good chance that when you run this code you will see the last element printed for every iteration instead of each value in sequence, because the goroutines will probably not begin executing until after the loop.

The solution

The correct way of writing the above code is:


for val := range values {
 go func(val interface{}) {
  fmt.Println(val)
 }(val)
}

Here, val is passed into goroutine as a parameter, and each val is computed separately and saved to the stack of goroutine to get the desired result.

Another method is to define new variables in the loop. Since variables defined in the loop are not Shared during the loop traversal, the same effect can be achieved:


for i := range valslice {
 val := valslice[i]
 go func() {
  fmt.Println(val)
 }()
}

For the problem mentioned at the beginning of this article, the simplest solution is to add a temporary variable inside the loop and replace all i in goroutine with this temporary variable:


server := i

conclusion


Related articles: