duangouyan3328 2017-11-12 01:37
浏览 65
已采纳

这是因为go编译器优化了代码吗?

package main

import "time"

func main() {
    i := 1
    go func() {
        for {
            i++
        }
    }()
    <-time.After(1 * time.Second)
    println(i)
}

The output is always 1.

However it's absolutely that 1s is enough for the for loop to go over many many times.

I think the i in the closure is the i in the main func.

See the code below.

package main

import "time"

func main() {
    i := 1
    go func() {
        for {
            i++
            println("+1")
        }
    }()
    <-time.After(1 * time.Second)
    println(i)
}

After many lines of "+1", the output is exactly a great number as expected.

  • 写回答

2条回答 默认 最新

  • dtdh11647 2017-11-12 05:27
    关注

    The Go Memory Model

    Version of May 31, 2014

    Introduction

    The Go memory model specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine.

    Advice

    Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.

    To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages.

    If you must read the rest of this document to understand the behavior of your program, you are being too clever.

    Don't be clever.

    Synchronization

    var a string
    
    func hello() {
      go func() { a = "hello" }()
      print(a)
    }
    

    the assignment to a is not followed by any synchronization event, so it is not guaranteed to be observed by any other goroutine. In fact, an aggressive compiler might delete the entire go statement.


    The assignment to i, via increment i++ (i = i + 1), is not followed by any synchronization event, so it is not guaranteed to be observed by any other goroutine. In fact, an aggressive compiler might delete the entire i++ statement.

    For example,

    package main
    
    import "time"
    
    func main() {
        i := 1
        go func() {
            for {
                i++
            }
        }()
        <-time.After(1 * time.Millisecond)
        println(i)
    }
    

    Output:

    1
    

    The goroutine reduces to:

    "".main.func1 STEXT nosplit size=2 args=0x8 locals=0x0
        0x0000 00000 (elide.go:7)   TEXT    "".main.func1(SB), NOSPLIT, $0-8
        0x0000 00000 (elide.go:7)   FUNCDATA    $0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
        0x0000 00000 (elide.go:7)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (elide.go:9)   JMP 0
    

    To the compiler,

    for {
        i++
    }
    

    can be implemented by incrementing a register forever, essentially a no-op for loop.

    for { }
    

    After inserting a print statement,

    package main
    
    import "time"
    
    func main() {
        i := 1
        go func() {
            for {
                i++
                println("+1")
            }
        }()
        <-time.After(1 * time.Millisecond)
        println(i)
    }
    

    Output:

    +1
    +1
    << SNIP >>
    +1
    +1
    432
    

    The goroutine expands to,

    "".main.func1 STEXT size=81 args=0x8 locals=0x18
        0x0000 00000 (elide.go:7)   TEXT    "".main.func1(SB), $24-8
        0x0000 00000 (elide.go:7)   MOVQ    (TLS), CX
        0x0009 00009 (elide.go:7)   CMPQ    SP, 16(CX)
        0x000d 00013 (elide.go:7)   JLS 74
        0x000f 00015 (elide.go:7)   SUBQ    $24, SP
        0x0013 00019 (elide.go:7)   MOVQ    BP, 16(SP)
        0x0018 00024 (elide.go:7)   LEAQ    16(SP), BP
        0x001d 00029 (elide.go:7)   FUNCDATA    $0, gclocals·a36216b97439c93dafebe03e7f0808b5(SB)
        0x001d 00029 (elide.go:7)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x001d 00029 (elide.go:8)   MOVQ    "".&i+32(SP), AX
        0x0022 00034 (elide.go:9)   INCQ    (AX)
        0x0025 00037 (elide.go:10)  PCDATA  $0, $0
        0x0025 00037 (elide.go:10)  CALL    runtime.printlock(SB)
        0x002a 00042 (elide.go:10)  LEAQ    go.string."+1
    "(SB), AX
        0x0031 00049 (elide.go:10)  MOVQ    AX, (SP)
        0x0035 00053 (elide.go:10)  MOVQ    $3, 8(SP)
        0x003e 00062 (elide.go:10)  PCDATA  $0, $0
        0x003e 00062 (elide.go:10)  CALL    runtime.printstring(SB)
        0x0043 00067 (elide.go:10)  PCDATA  $0, $0
        0x0043 00067 (elide.go:10)  CALL    runtime.printunlock(SB)
        0x0048 00072 (elide.go:9)   JMP 29
        0x004a 00074 (elide.go:9)   NOP
        0x004a 00074 (elide.go:7)   PCDATA  $0, $-1
        0x004a 00074 (elide.go:7)   CALL    runtime.morestack_noctxt(SB)
        0x004f 00079 (elide.go:7)   JMP 0
    

    The increased complexity of the goroutine means that the compiler no longer considers dedicating a register to the value of i. The in-memory value of i is incremented, which makes the updates visible, with a data race, to the main goroutine.

    ==================
    WARNING: DATA RACE
    
    Read at 0x00c420094000 by 
    main goroutine:
      main.main()
          /home/peter/gopath/src/lucky.go:14 +0xac
    
    Previous write at 0x00c420094000 by 
    goroutine 5:
      main.main.func1()
          /home/peter/gopath/src/lucky.go:9 +0x4e
    
    Goroutine 5 (running) created at:
      main.main()
          /home/peter/gopath/src/lucky.go:7 +0x7a
    ==================
    

    For your expected result, add some synchronization,

    package main
    
    import (
        "sync"
        "time"
    )
    
    func main() {
        mx := new(sync.Mutex)
        i := 1
        go func() {
            for {
                mx.Lock()
                i++
                mx.Unlock()
            }
        }()
        <-time.After(1 * time.Second)
        mx.Lock()
        println(i)
        mx.Unlock()
    }
    

    Output:

    41807838
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥20 ue5运行的通道视频都会有白色锯齿
  • ¥20 用雷电模拟器安装百达屋apk一直闪退
  • ¥15 算能科技20240506咨询(拒绝大模型回答)
  • ¥15 自适应 AR 模型 参数估计Matlab程序
  • ¥100 角动量包络面如何用MATLAB绘制
  • ¥15 merge函数占用内存过大
  • ¥15 Revit2020下载问题
  • ¥15 使用EMD去噪处理RML2016数据集时候的原理
  • ¥15 神经网络预测均方误差很小 但是图像上看着差别太大
  • ¥15 单片机无法进入HAL_TIM_PWM_PulseFinishedCallback回调函数