菜鸟问一个关于 goroutine 和闭包的问题

2018-10-26 20:33:53 +08:00
 IsaacYoung

看到这样一段代码,有大佬给解释下pointer receiver对 goroutine 执行的影响,或者说解释下这段代码三个函数表现行为的具体原因,谢谢了🙏

playground 地址: https://play.golang.org/p/cKrC0oy7FP

package main

import (
	"fmt"
	"time"
)

type field struct {
    name string
}

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

func TestClosure() {

    data := []*field{{"one"}, {"two"}, {"three"}}

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

    time.Sleep(3 * time.Second)
}

func TestClosure1() {

    data := []field{{"one"}, {"two"}, {"three"}}

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

    time.Sleep(3 * time.Second)
}

func TestClosure2() {

    data := []*field{{"one"}, {"two"}, {"three"}}

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

    time.Sleep(3 * time.Second)
}

func main(){
	TestClosure()
	fmt.Println("----")
	TestClosure1()
	fmt.Println("----")
	TestClosure2()	
}

2332 次点击
所在节点    Go 编程语言
6 条回复
wqlin
2018-10-26 20:52:58 +08:00
首先看 TestClosure2 ; 犯了常见错误,可以参考: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
应该改成:
```
func TestClosure2() {

data := []*field{{"one"}, {"two"}, {"three"}}

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

time.Sleep(3 * time.Second)
}
```
理解了 TestClosure2 之后来看 TestClose1。这里要理解 function receiver ;可以参考: https://tour.golang.org/methods/4。关键是这句话
>
With a value receiver, the Scale method operates on a copy of the original Vertex value. (This is the same behavior as for any other function argument.)
>
所以要实现同样的效果,可以改成:
```
func TestClosure1() {

data := []field{{"one"}, {"two"}, {"three"}}

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

time.Sleep(3 * time.Second)
}
```

最后放 playground: https://play.golang.org/p/2C71XfJm2SI
wqlin
2018-10-26 21:00:48 +08:00
@wqlin #1
TestClosure1 也可以改成:
```
func TestClosure1() {

data := []field{{"one"}, {"two"}, {"three"}}

for i := range data {
go data[i].print()
}

time.Sleep(3 * time.Second)
}
```
实现同样的效果
icexin
2018-10-26 21:35:24 +08:00
代码改成这样你就能明白了
``` go
package main

import (
"fmt"
"time"
)

type field struct {
name string
}

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

func TestClosure() {

data := []*field{{"one"}, {"two"}, {"three"}}

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

time.Sleep(3 * time.Second)
}

func TestClosure1() {

data := []field{{"one"}, {"two"}, {"three"}}

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

time.Sleep(3 * time.Second)
}

func TestClosure2() {

data := []*field{{"one"}, {"two"}, {"three"}}

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

time.Sleep(3 * time.Second)
}

func main(){
TestClosure()
fmt.Println("----")
TestClosure1()
fmt.Println("----")
TestClosure2()
}

```

`go print(v)`会在执行函数前把参数(v)以值拷贝的方式固定住
`go func(){print(v)}` 会在执行的时候再获取参数(v)的值
reus
2018-10-26 21:35:46 +08:00
for 语句的循环变量,需要传入闭包时,应该先复制一份,因为每次循环都是使用同一个变量的

加 v := v 就可以复制了
cloudyplain
2018-10-27 07:59:01 +08:00
TestClosure,指针
TestClosure1,v.print() 等价 (&v).print,print 使用的是相同的地址,但指针内容在循环后被改变
TestClosure2, 闭包常见问题
Appla
2018-10-27 13:35:27 +08:00
https://golang.org/ref/spec#Go_statements
```
The function value and parameters are evaluated as usual in the calling goroutine, but unlike with a regular call, program execution does not wait for the invoked function to complete.
```
试试把 time.Sleep(1 * time.Second)加到执行 go 语句的后面看看

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/501537

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX