package main
import "time"
func main() {
i := 1
go func() {
for {
i++
}
}()
<-time.After(1 * time.Second)
println(i)
}
输出始终为1
。
然而,1s足以使for
循环经历多次。
我认为闭包中的i
是i
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;后,输出正好符合预期。
答案 0 :(得分:11)
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语句。
通过增量i
(i++
)对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?