关于匿名函数使用共享变量的问题

gpfly · 2019-03-28 10:38:33 · 1473 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2019-03-28 10:38:33 的主题,其中的信息可能已经有所发展或是发生改变。

环境:go1.7.3 win7 64bit

代码1的执行结果是隔一秒只打印0,不符合预期

0
0
0
...

把代码1改成代码2,结果就对了

353036023
713389754
1073135313
...

这是个什么情况,有大佬知道吗

代码1:

package main

import (
    "fmt"
    "time"
)

func main() {
    i := 0
    go func () {
        for true {
            //隔一秒打印i
            time.Sleep(1000000000)
            fmt.Println(i)
        }
    }()

    for true {
        i++
    }
}

代码2:

package main

import (
    "fmt"
    "time"
)

func main() {
    i := 0
    go func () {
        for true {
            i++
        }
    }()

    for true {
        //隔一秒打印i
        time.Sleep(1000000000)
        fmt.Println(i)
    }
}

有疑问加站长微信联系(非本文作者)

第 1 条附言  ·  2019-04-01 10:36:41

已解决这个问题,问题原因是go编译器默认优化了代码,在build的时候添加-gcflags '-N -l'禁用优化就可以了

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1473 次点击  
加入收藏 微博
13 回复  |  直到 2019-04-06 09:29:00
lightfish-zhang
lightfish-zhang · #1 · 6年之前

这个问题在我电脑上,两种情况打印出的结果都是 0。 环境:go version go1.12 linux/amd64

BruceWangNo1
BruceWangNo1 · #2 · 6年之前

这个问题在我电脑上,两种情况打印出的结果都是 0。 环境:go version go1.12 linux/amd64

真奇怪,我记得上午我测试的时结果是不一样的。然后刚才我又测试了一下,怎么结果都是0了?题主确定结果不一样?

sheepbao
sheepbao · #3 · 6年之前

image.png 编译器直接没生成 i++ 的汇编代码,不知道为何编译器直接没编译出 i++ 这个命令,可能编译器做了判断这种简单的for是没意义的。改复杂一点,编译器识别不出来,就可以了。如下图

image.png

sheepbao
sheepbao · #4 · 6年之前

上面的截图不好看代码,我发这里,你可以自己研究一下

func testg1() {
    i := int64(0)
    go func() {
        for true {
            //隔一秒打印i
            time.Sleep(1000000000)
            fmt.Println(i) // 0
            // runtime.GC()   // forever block
        }
    }()

    for true {
        i++
    }
}

func testg2() {
    i := int64(0)
    go func() {
        for true {
            //隔一秒打印i
            time.Sleep(1000000000)
            fmt.Println(atomic.LoadInt64(&i)) // 778880009
        }
    }()

    for true {
        atomic.AddInt64(&i, 1)
    }
}

func testg4() {
    i := int64(0)
    go func() {
        for true {
            i++
        }
    }()

    for true {
        //隔一秒打印i
        time.Sleep(1000000000)
        fmt.Println(i) // 0
        // runtime.GC()   // forever block
    }
}

func testg5() {
    i := int64(0)
    go func() {
        for true {
            //隔一秒打印i
            time.Sleep(1000000000)
            fmt.Println(i) // xxxx
            // runtime.GC()   // forever block
        }
    }()

    for true {
        f := func() {
            i++
            y := int64(0)
            y++
            _ = y * i
        }
        f()
    }
}

生成汇编代码,用 go tool compile -S go文件

BruceWangNo1
BruceWangNo1 · #5 · 6年之前

Golang大佬Ross Cox说了:“Don’t communicate by sharing memory. (Especially without any synchronization.)。” 具体原因请看“https://golang.org/ref/mem”。楼上汇编分析有道理。

marlonche
marlonche · #6 · 6年之前

当data race存在时,程序运行的行为是不确定的,所以这样的代码没有实际意义

gpfly
gpfly · #7 · 6年之前

这个问题在我电脑上,两种情况打印出的结果都是 0。 环境:go version go1.12 linux/amd64

我后面用ubuntu go 1.10试了下,也都是0

gpfly
gpfly · #8 · 6年之前
sheepbaosheepbao #3 回复

![image.png](https://static.studygolang.com/190329/890d6aad9919001cb2259022fc7c3e6f.png) 编译器直接没生成 i++ 的汇编代码,不知道为何编译器直接没编译出 i++ 这个命令,可能编译器做了判断这种简单的for是没意义的。改复杂一点,编译器识别不出来,就可以了。如下图 ![image.png](https://static.studygolang.com/190329/eafc9d38c0e6e173819ac7978841ae0b.png)

对的,后面我在两个循环里面都打印i,结果就不是0了

sheepbao
sheepbao · #9 · 6年之前
gpflygpfly #8 回复

#3楼 @sheepbao 对的,后面我在两个循环里面都打印i,结果就不是0了

如果你在循环都加了打印,是有区别的,编译器会插入调度代码。

gpfly
gpfly · #10 · 6年之前
BruceWangNo1BruceWangNo1 #5 回复

Golang大佬Ross Cox说了:“Don’t communicate by sharing memory. (Especially without any synchronization.)。” 具体原因请看“https://golang.org/ref/mem”。楼上汇编分析有道理。

这个网页我打不开, 我是初学,各种想法都想试下,以后会避开这个坑

gpfly
gpfly · #11 · 6年之前
marlonchemarlonche #6 回复

当data race存在时,程序运行的行为是不确定的,所以这样的代码没有实际意义

我觉得不是data race的问题,应该是编译器优化的问题

gpfly
gpfly · #12 · 6年之前
sheepbaosheepbao #9 回复

#8楼 @gpfly 如果你在循环都加了打印,是有区别的,编译器会插入调度代码。

初学go,不太懂go的编译优化策略,谢谢你的方法

BruceWangNo1
BruceWangNo1 · #13 · 6年之前
gpflygpfly #10 回复

#5楼 @BruceWangNo1 这个网页我打不开, 我是初学,各种想法都想试下,以后会避开这个坑

复制粘贴链接

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传