小白求助 怎么都想不明白这个运行结果是怎么得到的

a1al · 2021-10-27 20:52:10 · 1438 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2021-10-27 20:52:10 的主题,其中的信息可能已经有所发展或是发生改变。

最近在学习go并发和同步相关的知识,关于channel尤其是单缓冲channel有很多疑问,也感谢之前很多大佬的帮助。 在晚上敲了一个关于Once的demo,代码如下

package main

import (
    "fmt"
    "sync"
)

func main() {
    doOnve()
}

func doOnve() {
    var once sync.Once
    funBody := func() {  //被检测的是否只执行1次的函数对象
        fmt.Println("Only once")
    }

    c := make(chan int) //无缓冲channel

    for i:=0;i<10;i++{
        go func() { //10个协程
            once.Do(funBody)
            c <- i
        }()
    }
    for i := 0; i < 10; i++ {
        fmt.Println( <- c)
    }
}

运行结果如下微信截图_20211027204914.png

Once的作用体现出来了但是实在不理解为什么会打印出10个10呢(特别是我的i在循环中都没遍历到10)?有没有大佬能帮小白解释一下,感激不尽!


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

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

1438 次点击  
加入收藏 微博
11 回复  |  直到 2021-11-02 10:40:59
whoam1
whoam1 · #1 · 3年之前

1、for 循环中的临时变量使用的为相同的一个地址空间、因此for循环完毕时临时变量的值已改变、导致协程中传递到通道的值也会不符合预期。

2、在for循环中起协程使用临时变量需要再次将临时变量的值拷贝一份及赋值到新的变量空间。

3、去除once的执行、会看到会有不同的数字输出、因为for循环和协程是异步的、又因为1的原因、所以值不确定。

4、加上once的执行,每次协程都会先once判断一次才将数据传递到通道,for循环已遍历完毕,因此临时变量的值为最后一值。

5、for循环临时变量会在执行加1操作后,在判断条件、因此临时变量值为10。

a1al
a1al · #2 · 3年之前
whoam1whoam1 #1 回复

1、for 循环中的临时变量使用的为相同的一个地址空间、因此for循环完毕时临时变量的值已改变、导致协程中传递到通道的值也会不符合预期。 2、在for循环中起协程使用临时变量需要再次将临时变量的值拷贝一份及赋值到新的变量空间。 3、去除once的执行、会看到会有不同的数字输出、因为for循环和协程是异步的、又因为1的原因、所以值不确定。 4、加上once的执行,每次协程都会先once判断一次才将数据传递到通道,for循环已遍历完毕,因此临时变量的值为最后一值。 5、for循环临时变量会在执行加1操作后,在判断条件、因此临时变量值为10。

老哥 我懂了 我忽略了闭包是同地址的参数

改成下面这样完美解决

    for i:=0;i<10;i++{
        go func(x int) {
            once.Do(funBody)
            //c <- i  //若是这样写 输出结果会是 10 10 10...10 因为 闭包参数是外面的10 ;正确方法是为该匿名函数设置参数 并按参数输出
            c <- x
        }(i)
    }

image.png

a1al
a1al · #3 · 3年之前
whoam1whoam1 #1 回复

1、for 循环中的临时变量使用的为相同的一个地址空间、因此for循环完毕时临时变量的值已改变、导致协程中传递到通道的值也会不符合预期。 2、在for循环中起协程使用临时变量需要再次将临时变量的值拷贝一份及赋值到新的变量空间。 3、去除once的执行、会看到会有不同的数字输出、因为for循环和协程是异步的、又因为1的原因、所以值不确定。 4、加上once的执行,每次协程都会先once判断一次才将数据传递到通道,for循环已遍历完毕,因此临时变量的值为最后一值。 5、for循环临时变量会在执行加1操作后,在判断条件、因此临时变量值为10。

谢谢老哥!

dagongrenzzZ
dagongrenzzZ · #4 · 3年之前
    package main

import (
    "fmt"
    "sync"
)

func main() {
    doOnve()
}

func doOnve() {
    var once sync.Once
    funBody := func() {  //被检测的是否只执行1次的函数对象
        fmt.Println("Only once")
    }

    c := make(chan int) //无缓冲channel

    for i:=0;i<10;i++{
        go func(i int) { //10个协程
            once.Do(funBody)
            c <- i
        }(i)
    }
    for i := 0; i < 10; i++ {
        fmt.Println( <- c)
    }
}

麻烦删下楼,没刷新页面,回晚了

a1al
a1al · #5 · 3年之前
dagongrenzzZdagongrenzzZ #4 回复

```go package main import ( "fmt" "sync" ) func main() { doOnve() } func doOnve() { var once sync.Once funBody := func() { //被检测的是否只执行1次的函数对象 fmt.Println("Only once") } c := make(chan int) //无缓冲channel for i:=0;i<10;i++{ go func(i int) { //10个协程 once.Do(funBody) c <- i }(i) } for i := 0; i < 10; i++ { fmt.Println( <- c) } } ``` 麻烦删下楼,没刷新页面,回晚了

没事的也谢谢老哥了!

1034992601
1034992601 · #6 · 3年之前

你的测试结果都是一样, 里面有一个点:for里的i是逃逸到堆上的,因此每个goroutine里实际是引用的一个地址值(这个地址值会变化的,它的最后变化值是10), 在多核(多p)情况下,可能会有其它值的输出

wangameng
wangameng · #7 · 3年之前

channel存的是地址不是123456789

a1al
a1al · #8 · 3年之前
10349926011034992601 #6 回复

你的测试结果都是一样, 里面有一个点:for里的i是逃逸到堆上的,因此每个goroutine里实际是引用的一个地址值(这个地址值会变化的,它的最后变化值是10), 在多核(多p)情况下,可能会有其它值的输出

感谢 已经知道到哪出问题啦

a1al
a1al · #9 · 3年之前
wangamengwangameng #7 回复

channel存的是地址不是123456789

感谢!

entrehuihui
entrehuihui · #10 · 3年之前

一看10,10,10, 代码都不用看就知道是什么原因了

rustgo20
rustgo20 · #11 · 3年之前

就是运行了最后一个

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