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

时间:2017-11-12 01:37:18

标签: go goroutine

package main

import "time"

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

输出始终为1

然而,1s足以使for循环经历多次。

我认为闭包中的ii func中的main

请参阅下面的代码。

package main

import "time"

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

经过多行&#34; + 1&#34;后,输出正好符合预期。

2 个答案:

答案 0 :(得分:11)

  

The Go Memory Model

     

2014年5月31日的版本

     

<强>简介

     

Go内存模型指定读取a的条件   一个goroutine中的变量可以保证观察产生的值   通过在不同的goroutine中写入相同的变量。

     

<强>建议

     

修改多个同时访问的数据的程序   goroutines必须序列化这种访问。

     

要序列化访问权限,请使用通道操作或其他操作保护数据   同步原语,例如sync和sync / atomic中的同步原语   包。

     

如果您必须阅读本文档的其余部分以了解该行为   你的计划,你太聪明了。

     

不要聪明。

     

<强>同步

var a string

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

对a的赋值后面没有任何同步事件,所以   它不能保证被任何其他goroutine观察到。事实上,   积极的编译器可能会删除整个go语句。

通过增量ii++)对i = i + 1的分配后面没有任何同步事件,因此不保证任何其他goroutine都能观察到它。实际上,积极的编译器可能会删除整个i++语句。

例如,

package main

import "time"

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

输出:

1

goroutine减少为:

"".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

编译器,

for {
    i++
}

可以通过永久递增寄存器来实现,基本上是一个无操作for循环。

for { }

插入print语句后

package main

import "time"

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

输出:

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

goroutine扩展为,

"".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\n"(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

goroutine的复杂性增加意味着编译器不再考虑将寄存器专用于i的值。 i的内存中值增加,这使得更新在数据竞争中可见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
==================

对于您的预期结果,请添加一些同步

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()
}

输出:

41807838

答案 1 :(得分:1)

需要同步对变量i的并发访问:

  

同步最好通过渠道或设施完成   同步包。通过沟通分享记忆;不要通过沟通   分享记忆。

参考:https://golang.org/pkg/sync/atomic/

这很有趣,所以我分享了我的实验:

0-您的代码使用time.Sleep(1 * time.Second)不推荐 - 未同步):

package main

import "time"

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

输出:

1

1-使用i := new(int)不推荐 - 未同步):

package main

import "time"

func main() {
    i := new(int)
    go func() {
        for {
            *i++
        }
    }()
    time.Sleep(1 * time.Second)
    println(*i)
}

输出(CPU:i7-7700K @ 4.2GHz):

772252413

2-使用atomic.AddInt64(&i, 1)Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms)同步:

package main

import (
    "sync/atomic"
    "time"
)

func main() {
    i := int64(1)
    go func() {
        for {
            atomic.AddInt64(&i, 1) // free running counter
        }
    }()
    time.Sleep(1 * time.Second)
    println(atomic.LoadInt64(&i)) // sampling the counter
}

输出:

233008800

3-使用chan int进行同步:

package main

import "time"

func main() {
    ch := make(chan int)
    go func() {
        timeout := time.NewTimer(1 * time.Second)
        defer timeout.Stop()
        i := 1
        for {
            select {
            case <-timeout.C:
                ch <- i
                return
            default:
                i++
            }
        }
    }()
    //time.Sleep(1 * time.Second)
    println(<-ch)
}

输出:

272702341

4-使用sync.WaitGroup进行同步:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var done sync.WaitGroup
    done.Add(1)
    i := 1
    go func() {
        defer done.Done()
        timeout := time.NewTimer(1 * time.Second)
        defer timeout.Stop()
        for {
            select {
            case <-timeout.C:
                return
            default:
                i++
            }
        }
    }()
    done.Wait()
    fmt.Println(i)
}

输出:

261459418

5-使用退出通道进行同步:

package main

import (
    "fmt"
    "time"
)

func main() {
    quit := make(chan struct{})
    i := 1
    go func() {
        for {
            i++
            select {
            case <-quit:
                return
            default:
            }
        }
    }()
    time.Sleep(1 * time.Second)
    quit <- struct{}{}
    fmt.Println(i)
}

输出:

277366276

6-使用sync.RWMutex进行同步:

package main

import (
    "sync"
    "time"
)

func main() {
    var i rwm
    go func() {
        for {
            i.inc() // free running counter
        }
    }()
    time.Sleep(1 * time.Second)
    println(i.read()) // sampling the counter
}

type rwm struct {
    sync.RWMutex
    i int
}

func (l *rwm) inc() {
    l.Lock()
    defer l.Unlock()
    l.i++
}
func (l *rwm) read() int {
    l.RLock()
    defer l.RUnlock()
    return l.i
}

输出:

24271318

相关主题:

The Go Memory Model
There is no equivalent to volatile and register in Go
Does Go support volatile / non-volatile variables?