V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
kopp123
V2EX  ›  Go 编程语言

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

  •  
  •   kopp123 · 2016-12-30 18:09:09 +08:00 · 2720 次点击
    这是一个创建于 2920 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 。 输出是什么。

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

    另外 file := file ,就是每个文件了。
    wangxiyu191
        4
    wangxiyu191  
       2016-12-30 19:17:47 +08:00
    @kopp123 那说明前四个负责 printf 的 goroutine 被执行的时候 for 循环还没跑完。
    tyzual
        5
    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
        6
    kopp123  
    OP
       2016-12-30 19:37:56 +08:00
    @sakeven 你是对的。
    kopp123
        7
    kopp123  
    OP
       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
        8
    CRVV  
       2016-12-30 20:09:40 +08:00
    https://golang.org/doc/effective_go.html#channels

    和楼主问的问题完全一样的例子
    tyzual
        9
    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
        10
    kopp123  
    OP
       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
        11
    zts1993  
       2016-12-31 00:18:17 +08:00 via Android
    结果看这里 thumbnail.ImageFile(file)

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

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

    另, 楼主的`file=file`加这句代码的意义是什么?
    再另,楼主 10 楼的代码里面 应该是`fmt.Println(f)`才会有遍历的效果就是了。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1335 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 17:46 · PVG 01:46 · LAX 09:46 · JFK 12:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.