Preface
When implementing a business recently, you need to read the data and then process it asynchronously; it is naturally easier to implement in Go. The pseudo code is as follows:
list := []*Demo{{"a"}, {"b"}}
for _, v := range list {
go func() {
fmt.Println("name="+v.Name)
}()
}
type Demo struct {
Name string
}
<!--more-->
It seems that a few lines of code are very simple, but it does not meet our expectations. After printing, the output is:
name=b
name=b
Not what we expected:
name=a
name=b
Pit one
As my qualifications for writing go are still shallow and bug
I thought it was a data source problem at first, and went through several rounds of self-doubt. In short, the process is not shown, let's see how to fix this problem.
First, the first method is to use temporary variables:
list := []*Demo{{"a"}, {"b"}}
for _, v := range list {
temp:=v
go func() {
fmt.Println("name="+temp.Name)
}()
}
In this way, it can be output correctly, in fact, the clues of the problem can also be seen from this way of writing.
When the first type does not use temporary variables, the main coroutine runs quickly, and the printed sub-coroutine may not be running yet; when it starts to run, the v here has been assigned the last one.
So what is printed here is always the last variable.
The use of temporary variables will make a copy of the current traversed value, which will naturally not affect each other.
Of course, in addition to temporary variables, closures can also be used.
list := []*Demo{{"a"}, {"b"}}
for _, v := range list {
go func(temp *Demo) {
fmt.Println("name="+temp.Name)
}(v)
}
When passing parameters through closures, each goroutine
will store a copy of the parameters in its own stack, so that it can be distinguished.
Pit two
Similarly, there is a second pit:
list2 := []Demo{{"a"}, {"b"}}
var alist []*Demo
for _, test := range list2 {
alist = append(alist, &test)
}
fmt.Println(alist[0].Name, alist[1].Name)
This code does not match our expectations:
b b
But we can modify it slightly:
list2 := []Demo{{"a"}, {"b"}}
var alist []Demo
for _, test := range list2 {
fmt.Printf("addr=%p\n", &test)
alist = append(alist, test)
}
fmt.Println(alist[0].Name, alist[1].Name)
addr=0xc000010240
addr=0xc000010240
a b
By the way, the memory address is printed, in fact, the reason can be guessed from the result; the memory address printed by each traversal is the same, so if we store pointers, essentially the contents of the same memory address are stored, so The value is the same.
And if we only store the value and don't store the pointer, there will be no such problem.
But what if you want to use pointers?
list2 := []Demo{{"a"}, {"b"}}
var alist []*Demo
for _, test := range list2 {
temp := test
//fmt.Printf("addr=%p\n", &test)
alist = append(alist, &temp)
}
fmt.Println(alist[0].Name, alist[1].Name)
It's also simple, just use temporary variables in the same way.
From the official source code, we can know that for range
is just syntactic sugar, and is essentially a for loop; because every time the same object is traversed and assigned, such an "oolong" will appear.
defer's pit
for
loop + defer
is also a combination pit (although this is not recommended), let's look at an example first:
// demo1
func main() {
a := []int{1, 2, 3}
for _, v := range a {
defer fmt.Println(v)
}
}
// demo2
func main() {
a := []int{1, 2, 3}
for _, v := range a {
defer func() {
fmt.Println(v)
}()
}
}
Output separately:
//demo1
3
2
1
//demo2
3
3
3
demo1
is easy to understand, and defer
can be understood as putting the execution statement on the stack, so the result presented is first-in-last-out.
In demo2
, because it is a closure, the closure holds a reference to the variable v, so v has been assigned to the last value when the final execution is delayed, so the printing is the same.
The solution is similar to the above, it can be solved by passing in the parameters:
for _, v := range a {
defer func(v int) {
fmt.Println(v)
}(v)
}
There is a high probability that this kind of detailed problem will not be encountered in daily development. The most likely one is the interview, so it does not hurt to know more about it.
Summarize
Similar to the first case where goroutine
called in the for loop, I think IDE
can be fully reminded; for example, IDEA
contains most of the errors that may be considered possible, and I look forward to the follow-up goland
update.
But in fact, these kinds of errors have been reminded on the official blog.
https://github.com/golang/go/wiki/CommonMistakes#using-reference-to-loop-iterator-variable
It's just that most people probably haven't read it. After this, I have to take the time to read it.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。