pointers - `go print(v)` 和 `go func() { print(v) }()` 之间的区别?

标签 pointers go closures

代码如下:

type field struct {
    name string
}

func print(p *field) {
    fmt.Println(p.name)
}

func fix1() {
    data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
    for _, v := range data {
        go print(v)
    }
    time.Sleep(time.Millisecond * 200)
}

func wrong1() {
    data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
    for _, v := range data {
        go func() {
            print(v)
        }()
    }
    time.Sleep(time.Millisecond * 200)
}

func main() {
    wrong1()
}

据我所知,函数 wrong1 中的所有 goroutine 共享同一个局部变量 v。在goroutine执行的那一刻,v的值可能等于data中的任意值,因此函数会打印3次随机数据。

但是,我无法理解为什么函数 fix1 的行为不同(它将 data 中的每个值打印一次)。

最佳答案


wrong1(): go func() { print(v) }()


Go: Frequently Asked Questions (FAQ)

What happens with closures running as goroutines?

Some confusion may arise when using closures with concurrency. Consider the following program:

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

One might mistakenly expect to see a, b, c as the output. What you'll probably see instead is c, c, c. This is because each iteration of the loop uses the same instance of the variable v, so each closure shares that single variable. When the closure runs, it prints the value of v at the time fmt.Println is executed, but v may have been modified since the goroutine was launched.

To bind the current value of v to each closure as it is launched, one must modify the inner loop to create a new variable each iteration. One way is to pass the variable as an argument to the closure:

for _, v := range values {
    go func(u string) {
        fmt.Println(u)
        done <- true
    }(v)
}

In this example, the value of v is passed as an argument to the anonymous function. That value is then accessible inside the function as the variable u.

Even easier is just to create a new variable, using a declaration style that may seem odd but works fine in Go:

for _, v := range values {
    v := v // create a new 'v'.
    go func() {
        fmt.Println(v)
        done <- true
    }()
}

你的 wrong1 例子,

for _, v := range data {
    go func() {
        print(v)
    }()
}

Playground :https://play.golang.org/p/0w86nvVMt1g

输出:

three
three
three

你的 wrong1 示例,创建一个新变量,

for _, v := range data {
    v := v
    go func() {
        print(v)
    }()
}

Playground :https://play.golang.org/p/z5RCI0ZZU8Z

输出:

one
two
three

你的错误 1 示例,将变量作为参数传递,

for _, v := range data {
    go func(v *field) {
        print(v)
    }(v)
}

Playground :https://play.golang.org/p/1JVI7XYSqvv

输出:

one
two
three

fix1(): go print(v)


The Go Programming Language Specification

Calls

Given an expression f of function type F,

f(a1, a2, … an)

calls f with arguments a1, a2, … an. Except for one special case, arguments must be single-valued expressions assignable to the parameter types of F and are evaluated before the function is called.

Go statements

The function value and parameters are evaluated as usual in the calling goroutine.


您的 fix1 示例,在调用函数之前评估 v 的值,

for _, v := range data {
    go print(v)
}

Playground :https://play.golang.org/p/rN3UNaGi-ge

输出:

one
two
three

关于pointers - `go print(v)` 和 `go func() { print(v) }()` 之间的区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53987867/

相关文章:

go - 如何在未经验证的情况下从JWT token 获取声明

map - 创建全局 map 变量

循环内的 JavaScript 闭包——简单实用的例子

c++ - 以无符号整数存储缓冲区地址;

C++在构造函数中初始化一个非静态指针数组

c++无法将父类指针作为类型定义对静态模板成员的引用

json - 检查所需的 json 键(不是值)存在于 golang 中解析的 json 响应中

javascript - JS - 理解闭包

java - 什么是闭包? java有闭包吗?

c++ - 在堆中创建和使用数组