SoFunction
Updated on 2025-03-01

Go language uses goroutine to run closures and analyzes the pitfalls

introduce

In Go language, functions support anonymous functions, and closures are special anonymous functions that can be used to access variables outside the function body.

It should be noted that infor ... range ...In  , when using goroutine to execute closures, "pits" often fall.

Because anonymous functions can access variables outside the function body,for ... range ...The returned val value is the data of the same memory address referenced, so the val value outside the function body accessed by the anonymous function is the last output value in the loop.

"Training a Pit" sample code

func main() {
 done := make(chan bool)
 values := []string{"a", "b", "c"}
 for _, v := range values {
  go func() {
   (v)
   done <- true
  }()
 }
 // wait for all goroutines to complete before exiting
 for _ = range values {
  <-done
 }
}

Output result:

c

c

c

Read the above code,for ... range ...In  , use goroutine to execute the closure and print the elements in the slice. The actual output result is not the output result we expect.

This is because each iteration of the loop uses the same variable v instance, so each closure shares that single variable. We can simply modify it in the sample code, and output the memory address and value of the variable v.

Bundle(v)Modify to("val=%s pointer=%p\n", v, &v)

Modified output result:

val=c pointer=0xc000010200
val=c pointer=0xc000010200
val=c pointer=0xc000010200

We can find in the output result that the memory addresses of the print variable v are0xc000010200

When the closure is running, it will be executedwhen printing the value of the variable v, the value of v may have been modified after the goroutine is started. Interested readers can use itgo vetexamine.

How to avoid "falling into a trap"?

One way is to pass the variable as a parameter to the closure:

func main() {
 done := make(chan bool)
 values := []string{"a", "b", "c"}
 for _, v := range values {
  go func(param string) {
   // (v)
   ("val=%s pointer=%p\n", param, &param)
   done <- true
  }(v)
 }
 // wait for all goroutines to complete before exiting
 for _ = range values {
  <-done
 }
}

Output result:

val=c pointer=0xc000010200
val=a pointer=0xc00009a000
val=b pointer=0xc0000a4000

Read the above code and pass the value of the variable v as a parameter to the closure, and then, as the value of the formal parameter param, is accessed inside the function body.

Another way is to create a new variable

func main() {
 done := make(chan bool)
 values := []string{"a", "b", "c"}
 for _, v := range values {
  param := v
  go func() {
   // (v)
   ("val=%s pointer=%p\n", param, &param)
   done <- true
  }()
 }
 // wait for all goroutines to complete before exiting
 for _ = range values {
  <-done
 }
}

Output result:

val=c pointer=0xc000082200
val=a pointer=0xc0000821e0
val=b pointer=0xc0000821f0

By outputting the results, we can find that this method can also achieve the results we expect.

Summarize

We have introduced this article infor ... range ...In  , Go language does not define a new variable every time iteration, resulting in "pits" often falling out when running closures using goroutine.

We have given two ways to avoid "treating on pits". Among them, the second method is simpler. For more information about Go goroutine running closures, please follow my other related articles!