2
头图

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.


crossoverJie
5.4k 声望4k 粉丝