一段 golang 代码不太懂就 golang 大牛解读

2016-12-30 18:09:09 +08:00
 kopp123

func makethumbnail2(filename[]string){ ch:=make(chan struct{}) for _,file:=range filename{ file=file go func(){

		fmt.Println(file)
		thumbnail.ImageFile(file)
		ch<- struct {}{}
	}()
}
for range filename{
	<-ch
}

} filename 为 100 个图片文件的地址 slice 。 输出是什么。

2674 次点击
所在节点    Go 编程语言
13 条回复
morefreeze
2016-12-30 18:26:42 +08:00
输出不就是每个文件名么
你需要找本书看一眼
kopp123
2016-12-30 18:46:27 +08:00
@morefreeze 不懂别瞎 bb 按照理解应该是输出第 99 个文件路径。但是实际上确实随机输出 4 个文件地址才输出第 99 个文件地址,为啥是这样呢。
sakeven
2016-12-30 19:02:28 +08:00
这段代码输出第 99 个文件地址概率非常大,后面都是。跟循环和 gorountine 的切换有关。

另外 file := file ,就是每个文件了。
wangxiyu191
2016-12-30 19:17:47 +08:00
@kopp123 那说明前四个负责 printf 的 goroutine 被执行的时候 for 循环还没跑完。
tyzual
2016-12-30 19:22:14 +08:00
1. 贴代码的时候注意格式。你贴成这样鬼知道代码长啥样
2. 贴代码的时候把无关代码注释掉。比如 thumbnail.ImageFile(file)
3. 你上面贴的那一段代码格式化好以后大概长这样
===========================================
package main

import (
"fmt"
)

func makethumbnail2(filename []string) {
ch := make(chan struct{})
for _, file := range filename {
file = file
go func() {
fmt.Println(file)
//thumbnail.ImageFile(file)
ch <- struct{}{}
}()
}
for range filename {
<-ch
}
}

func main() {
var files []string
for i := 0; i < 100; i++ {
files = append(files, fmt.Sprintf("file%d", i))
}
makethumbnail2(files)
}

====================================
4. 下面来解答你的问题

1.1 你在 func() 里面 捕获了 file 变量。而 go 的捕获机制是按照引用捕获。 也就是说,如果我们在 func()外部改变 file 的值, func()内部的 file 也会受到影响
1.2 你的 func()是在另一个 goroutine 里面执行的。也就是说,一方面有 N 多个 func()在不同的 goroutine 里面执行。一方面第一个 for range 循环又在改变 file 的值。在第一个 for range 完成之前,你看到的输出应该是随缘产生的。而 for range 完成之后,则会输出最后一个文件名。
1.3 至于楼主的四个随机文件,那单纯是因为输出了 4 个文件以后,第一个 for range 完成了。
1.4 上面那段测试代码在我的环境下运行只输出了 10 个 file99
1.5 参考资料 https://www.goinggo.net/2014/06/pitfalls-with-closures-in-go.html
kopp123
2016-12-30 19:37:56 +08:00
@sakeven 你是对的。
kopp123
2016-12-30 19:48:13 +08:00
@tyzual 我可以这样理解吗? 可以把 range 循环体代码 和 go routine 代码 放在 2 个地方 。当 go routine 启动第一次执行时 range 已经跑了很多圈而起 go func 对 range 中的循环变量是引用捕获。如果 f:=file go func 中对 f 就是值捕获。
CRVV
2016-12-30 20:09:40 +08:00
https://golang.org/doc/effective_go.html#channels

和楼主问的问题完全一样的例子
tyzual
2016-12-30 20:17:21 +08:00
当 go routine 启动第一次执行时 range 已经跑了很多圈而起 go func 对 range 中的循环变量是引用捕获。
这句话可以这样理解


如果 f:=file go func 中对 f 就是值捕获。
go 中只有按引用捕获,所以如果在 go func 中引用 f 还是按引用捕获,只不过捕获的是 f 的引用。 go func 中的值会随着 f 变化而变化。
如果在 go func 中不想让捕获的值随着外部修改而变化的话应该把捕获的值当成一个参数传递。代码大概长这样
====================
func makethumbnail2(filename []string) {
ch := make(chan struct{})
for _, file := range filename {
go func(file string) {
fmt.Println(file)
//thumbnail.ImageFile(file)
ch <- struct{}{}
}(file)
}
for range filename {
<-ch
}
}
=================================
注意 go func 多了一个 file string 的参数,而 go func 结束的时候手动把 file 传递进去了。
因为 go func 有一个 file 参数,所以 go func 里面的 fmt.Println(file)的 file 实际上打印的是 go func 的参数。
而 go func 结束的时候在括号中传递的参数 file 是 for range 中当前的 file 。而 string 类型的参数是按照值的类型传递的。所以上面代码就会(无序)输出 file0 - file99
kopp123
2016-12-30 22:56:51 +08:00
@tyzual 你说的给 go func 加的参数方式我知道。实际上如果在 go func 外面 加 f:=file 不给 fo func 加参数也可以实现。

unc makethumbnail2(filename []string) {
ch := make(chan struct{})
for _, file := range filename {
f:=file
go func() {
fmt.Println(file)
//thumbnail.ImageFile()
ch <- struct{}{}
}()
}
for range filename {
<-ch
}
}
zts1993
2016-12-31 00:18:17 +08:00
结果看这里 thumbnail.ImageFile(file)

ch 这里只是等待所有 goroutine 完成而已
reus
2016-12-31 03:03:38 +08:00
应该用 file := file ,这样才会新建一个变量,不然就一直用 for 里的循环变量了。 for + go 的新手常见错误……
scnace
2016-12-31 11:10:33 +08:00
我觉得这样子比较好理解

[PlayGround]( https://play.golang.org/p/8Uh3CvraIQ)

另, 楼主的`file=file`加这句代码的意义是什么?
再另,楼主 10 楼的代码里面 应该是`fmt.Println(f)`才会有遍历的效果就是了。。。

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

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

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

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

© 2021 V2EX