package main import "time" func main() { i := 1 go func() { for { i++ } }() <-time.After(1 * time.Second) println(i) }
输出始终为1。
1
但是,绝对可以使for循环多次遍历1s 。
for
我认为,i在闭包是i在mainFUNC。
i
main
请参见下面的代码。
package main import "time" func main() { i := 1 go func() { for { i++ println("+1") } }() <-time.After(1 * time.Second) println(i) }
在多行“ +1”之后,输出正好是预期的很大数目。
记忆模型 2014年5月31日版本 介绍 Go内存模型指定了一种条件,在这种条件下,可以保证在一个goroutine中读取变量可以观察到在不同goroutine中写入同一变量所产生的值。 忠告 修改由多个goroutine同时访问的数据的程序必须序列化此类访问。 要序列化访问,请使用通道操作或其他同步原语(例如sync和sync / atomic包中的原语)保护数据。 如果您必须阅读本文档的其余部分以了解程序的行为,那么您就太聪明了。 别聪明 同步化 var a string func hello() { go func() { a = "hello" }() print(a) } 分配给a不会跟随任何同步事件,因此不能保证任何其他goroutine都会遵守它。实际上,积极的编译器可能会删除整个go语句。
记忆模型
2014年5月31日版本
介绍
Go内存模型指定了一种条件,在这种条件下,可以保证在一个goroutine中读取变量可以观察到在不同goroutine中写入同一变量所产生的值。
忠告
修改由多个goroutine同时访问的数据的程序必须序列化此类访问。
要序列化访问,请使用通道操作或其他同步原语(例如sync和sync / atomic包中的原语)保护数据。
如果您必须阅读本文档的其余部分以了解程序的行为,那么您就太聪明了。
别聪明
同步化
var a string func hello() { go func() { a = "hello" }() print(a) }
分配给a不会跟随任何同步事件,因此不能保证任何其他goroutine都会遵守它。实际上,积极的编译器可能会删除整个go语句。
i通过增量i++(i = i + 1)分配给时,没有任何同步事件,因此不能保证任何其他goroutine都会遵守该事件。实际上,积极的编译器可能会删除整个i++语句。
i++
i = i + 1
例如,
package main import "time" func main() { i := 1 go func() { for { i++ } }() <-time.After(1 * time.Millisecond) println(i) }
输出:
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语句后,
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会增加,这使得更新与数据竞争可见于maingoroutine。
================== 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