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

协程执行问题请教

  •  
  •   davidyanxw · 2020-04-21 19:35:44 +08:00 · 2306 次点击
    这是一个创建于 1465 天前的主题,其中的信息可能已经有所发展或是发生改变。
    package main

    import (
    "fmt"
    "time"
    )

    var x = 0

    func main() {
    var num = 123
    var p = &num
    c := make(chan int)
    go func() {
    c <- *p + x // 1. 返回 123
    // c <- *p // 2. 返回 789
    }()
    time.Sleep(time.Second)
    num = 789
    fmt.Println(<-c)

    }

    子协程,执行 1 返回 123,执行 2 返回 789
    没看出来有什么差别啊?
    16 条回复    2020-05-01 02:12:31 +08:00
    kaifang
        1
    kaifang  
       2020-04-21 19:53:05 +08:00
    有区别,
    1 进行了运算,取运算后的结果,你加了 1 秒的延时,很明显当时的 num 的值为 123,所以输出是 123,如果去掉延时,则为 789
    2 一直指向 num 的地址,从通道里读的时候读的是 789
    bwangel
        2
    bwangel  
       2020-04-21 20:56:20 +08:00
    time.Sleep 不能保证 goroutine 一定比 main 先执行,如果想要让 Goroutine 比 main 先执行的话,可以通过 waitGroup 来同步:

    https://gist.github.com/bwangelme/5e71895d40130521b71828cef72adc1f
    MoYi123
        3
    MoYi123  
       2020-04-21 21:21:21 +08:00   ❤️ 4
    c <- *p + x // 1. 返回 123 等于 tmp:= *p + x ; c<- tmp ,因为 chan 的 size 是 0 所以要等到主进程到 fmt.Println(<-c)时才会运行 c<- tmp
    blip
        4
    blip  
       2020-04-22 03:04:54 +08:00
    ”By default channels are unbuffered, meaning that they will only accept sends (chan <-) if there is a corresponding receive (<- chan) ready to receive the sent value“
    @MoYi123 正解
    useben
        5
    useben  
       2020-04-22 09:11:34 +08:00
    @MoYi123 正解, 1 2 楼答的不是这个问题
    davidyanxw
        6
    davidyanxw  
    OP
       2020-04-22 13:34:42 +08:00
    @blip
    按照这个说法的话,应该两个值都会返回 123 才对啊?
    为啥第 1 种返回 123,第 2 种返回 789
    davidyanxw
        7
    davidyanxw  
    OP
       2020-04-22 13:35:44 +08:00
    @kaifang 1 运算了,2 为啥不运算呢?
    kaifang
        8
    kaifang  
       2020-04-22 15:30:25 +08:00
    @davidyanxw #7 2,向通道发送的 num 的地址,此时通道是阻塞的,过一秒后 num 因为赋值 789 地址发生改变,然后从通道里读取 num 值是 789
    blip
        9
    blip  
       2020-04-23 00:11:51 +08:00
    @davidyanxw good ask, 我觉得我之前没有抓住对的点,我对 unbuffered channel 的解释其实并没有回答这个问题,这里的重点其实是
    1, *p+x 会 create 一个新的 int ( 123 )存放在另一个地址,这个地址存放的值没有改变过
    2,*p 会一直等于 num 的地址所存储的值,这个值在代码中被更改为 789
    所以才会造成两个 case 结果不同
    davidyanxw
        10
    davidyanxw  
    OP
       2020-04-24 11:59:32 +08:00
    @blip
    感谢

    ch <- expression

    expression 计算的时机是问题的关键。
    具体的细节,希望大牛们给详细说说
    guonaihong
        11
    guonaihong  
       2020-04-25 18:13:35 +08:00
    这个问题和抛硬币一样。
    c <-*p + x,既可能返回 123,也可能返回 789 。这和 thread 切换,先跑了哪个指令有关系(先跑了 num=789 还是后跑了),你可以把 sleep 时间修改小,证明这个现象。
    ```go
    package main

    import (
    "fmt"
    "time"
    )

    var x = 0

    func call() {
    var num = 123
    var p = &num
    c := make(chan int)
    go func() {
    c <- *p + x // 1. 返回 123
    // c <- *p // 2. 返回 789
    }()
    time.Sleep(time.Second / 1000)
    num = 789
    readValue := <-c
    if readValue == 789 {
    fmt.Printf("hello 789\n")
    }
    //fmt.Println(readValue)
    }

    func main() {
    for i := 0; i < 1000000; i++ {
    call()
    }
    }

    ```
    wsseo
        12
    wsseo  
       2020-04-26 16:52:17 +08:00
    @guonaihong 在 6c6t 的 cpu 上跑了一下,出现了一次 789 。
    davidyanxw
        13
    davidyanxw  
    OP
       2020-04-27 10:24:30 +08:00
    @guonaihong
    能详细解释下 1 这种情况既可能返回 123,也可能返回 789 吗?
    wsseo
        14
    wsseo  
       2020-04-28 14:09:09 +08:00
    @davidyanxw 主协程 sleep 1s,基本本可能输出 789 了。
    如果主协程 sleep 时间很短,那么 num = 789 可能会比*p + x 先执行
    guonaihong
        15
    guonaihong  
       2020-04-30 18:35:07 +08:00
    @wsseo @davidyanxw 这两天有点事就没有回答两位的问题。刚刚做了一些试验,发现了更有意思的地方。
    1.如果写 chan 的地方是算术表达式,go 会提前进行预处理(对 c<-*p+0),可能已经换成 c<-123,所有大概率返回 123
    2.如果是值,就换按正常逻辑往下走。time.Sleep 之后,两个 go 程开始了竞争,num=789 的执行速度,比唤醒生产者 chan+写数据快,所有大概率返回 789 。

    当然上面从数据中总结的规律特别依赖 go 的版本(go 1.13.1),大家也不要太在意。也许哪天人家(Go Core Team)就改了。就当乐一乐,原来 go 还有一些小动作。
    ```go
    package main

    import (
    "fmt"
    "time"
    )

    var x = 0

    func call() {
    var num = 123
    var p = &num
    c := make(chan int)
    go func() {
    //c <- *p + 0 //大概率返回 123
    c <- *p // 大概率返回 789
    }()
    time.Sleep(time.Second / 1000)
    num = 789
    readValue := <-c
    if readValue == 123 {
    fmt.Printf("hello %d\n", readValue)
    }
    }

    func main() {
    for i := 0; i < 1000000; i++ {
    call()
    }
    }

    ```
    CEBBCAT
        16
    CEBBCAT  
       2020-05-01 02:12:31 +08:00
    俺老孙直言,V2EX 这个防 spam 系统真的是人工智障

    [TIP - 如何在回复中贴代码]( /t/663565 )
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1084 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 19:15 · PVG 03:15 · LAX 12:15 · JFK 15:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.