The pit of golang-for range

In golang, the commonly used traversal methods include the classic for loop and for range. In fact, the control structure that uses the for range syntax should eventually be converted into a normal for loop by the golang compiler, so for range will actually be converted into a classic for loop and then executed, and it is this conversion process Often pits are left.
The following simply represents this conversion process: The
for range code is:

for index, value := range t_slice {
	original body
}

After conversion:

len_temp := len(t_slice)
range_temp := t_slice
for index_temp = 0; index_temp < len_temp; index_temp++ {
     value_temp := range_temp[index_temp]
     index := index_temp
     value := value_temp
     original body
}

Problems that may be encountered in actual coding

Will the cycle stop?

code show as below:

package main

import "fmt"

func main() {
	arr := []int{1,2}

	for _, v := range arr {
		arr = append(arr, v)
	}

	fmt.Println(arr)

}

result:

[1 2 1 2]
  • 1

From the above conversion process, we can see that the loop has already got the length of the loop before it starts, and it will not change during the loop.

Traverse all element addresses?

code show as below:

package main

import "fmt"

func main() {
	arr := []int{1,2}
	res := []*int{}

	for _, v := range arr {
		res = append(res, &v)
	}

	fmt.Println(*res[0], *res[1])
}

result:

2 2

It can be seen from the above conversion process that &v is actually the address of the same short variable inside the loop, so the same address is actually stored in res, and the value in this address is actually the value assigned in the last loop .
How to get all the element addresses?
The first way:

package main

import "fmt"

func main() {
	arr := []int{1,2}
	res := []*int{}

	for _, v := range arr {
		v := v
		res = append(res, &v)
	}

	fmt.Println(*res[0], *res[1])
}

In fact, a new short variable is introduced to replace the original value in the original body; the
second way:

package main

import "fmt"

func main() {
	arr := []int{1,2}
	res := []*int{}

	for i := range arr {
		res = append(res, &arr[i])
	}

	fmt.Println(*res[0], *res[1])
}

It is actually a for loop;
the result of these two ways:

1 2

Starting a coroutine during traversal?

code show as below:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var m = []int{1, 2, 3}
	var wg sync.WaitGroup
	for i := range m {
		wg.Add(1)
 		go func() {
			fmt.Print(i)
			wg.Done()
    	}()
	}
	wg.Wait()

}

Output result:

222

In the above code, the anonymous function is used to start the coroutine in each round of the loop. The anonymous function directly references the external variable i. In fact, this is the closure in golang. The closure is the combination of the anonymous function and the environment referenced by the anonymous function. The function has the feature of dynamic creation, which allows anonymous functions to directly reference external variables without passing parameters.
Each round of the loop starts a coroutine, and the coroutine start and the loop variable increment are not in the same coroutine, the speed of the coroutine start is much slower than the loop execution speed, so even when the first coroutine is just started, the loop variable may already be The increment is complete. Since all the coroutines share the loop variable i, and this i will be destroyed after the last coroutine using it ends, the final output result is the last value of the loop variable, which is 2.

package main

import (
	"fmt"
	"time"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	var m = []int{1, 2, 3}
	for i := range m {
		wg.Add(1)
 		go func() {
			fmt.Print(i)
			wg.Done()
		}()
		time.Sleep(time.Second)
	}
	wg.Wait()

}

Write in this way to increase the increment time interval of the loop variable to 1s, enough for the coroutine to start, and the output result at this time:

012

How to solve this problem more scientifically? In fact, similar to the previous one, there are two ways: the
first one:
pass in as a parameter:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var m = []int{1, 2, 3}
	var wg sync.WaitGroup
	for i := range m {
		wg.Add(1)
 		go func(i int) {
			fmt.Print(i)
			wg.Done()
    	}(i)
	}
	wg.Wait()

}

The second:
use local variable copy:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var m = []int{1, 2, 3}
	var wg sync.WaitGroup
	for i := range m {
		wg.Add(1)
		i := i
 		go func() {
			fmt.Print(i)
			wg.Done()
    	}()
	}
	wg.Wait()

}

Note:

  1. For large arrays, if you use for range traversal, the conversion process before traversal will be a waste of memory, which can be optimized:
    (1) traverse the array address for i, n := range &arr; (2) do slice reference for the array for i , n := range arr[:];

  2. For the traversal of large arrays to reset to the default value, golang bottom layer is optimized, so the efficiency is very high;

  3. Delete elements during map traversal. If the deleted elements are not traversed at first, they will not appear afterwards;

  4. New elements added during map traversal may be traversed;

Guess you like

Origin blog.csdn.net/feikillyou/article/details/112535192