这是因为go编译器优化了代码吗?
Is this because the go compiler optimized the code?
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
}
}()
<-time.After(1 * time.Second)
println(i)
}
输出总是1
。
然而,for
循环遍历很多次,1s 绝对足够了。
我认为闭包中的 i
是 main
函数中的 i
。
查看下面的代码。
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
println("+1")
}
}()
<-time.After(1 * time.Second)
println(i)
}
经过多行“+1”后,输出正好是一个很大的数字。
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.
通过递增 i++
(i = i + 1
) 对 i
的赋值不会跟随任何同步事件,因此不能保证任何其他 goroutine 都能观察到它。事实上,激进的编译器可能会删除整个 i++
语句。
例如,
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
}
}()
<-time.After(1 * time.Millisecond)
println(i)
}
输出:
1
协程缩减为:
"".main.func1 STEXT nosplit size=2 args=0x8 locals=0x0
0x0000 00000 (elide.go:7) TEXT "".main.func1(SB), NOSPLIT, [=13=]-8
0x0000 00000 (elide.go:7) FUNCDATA [=13=], gclocals·2a5305abe05176240e61b8620e19a815(SB)
0x0000 00000 (elide.go:7) FUNCDATA , 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), -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 , 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 [=18=], gclocals·a36216b97439c93dafebe03e7f0808b5(SB)
0x001d 00029 (elide.go:7) FUNCDATA , 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 [=18=], [=18=]
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 , 8(SP)
0x003e 00062 (elide.go:10) PCDATA [=18=], [=18=]
0x003e 00062 (elide.go:10) CALL runtime.printstring(SB)
0x0043 00067 (elide.go:10) PCDATA [=18=], [=18=]
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 [=18=], $-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
并发访问变量i
需要同步:
synchronization is better done with channels or the facilities of the
sync package. Share memory by communicating; don't communicate by
sharing memory.
参考: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?
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
}
}()
<-time.After(1 * time.Second)
println(i)
}
输出总是1
。
然而,for
循环遍历很多次,1s 绝对足够了。
我认为闭包中的 i
是 main
函数中的 i
。
查看下面的代码。
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
println("+1")
}
}()
<-time.After(1 * time.Second)
println(i)
}
经过多行“+1”后,输出正好是一个很大的数字。
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.
通过递增 i++
(i = i + 1
) 对 i
的赋值不会跟随任何同步事件,因此不能保证任何其他 goroutine 都能观察到它。事实上,激进的编译器可能会删除整个 i++
语句。
例如,
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
}
}()
<-time.After(1 * time.Millisecond)
println(i)
}
输出:
1
协程缩减为:
"".main.func1 STEXT nosplit size=2 args=0x8 locals=0x0
0x0000 00000 (elide.go:7) TEXT "".main.func1(SB), NOSPLIT, [=13=]-8
0x0000 00000 (elide.go:7) FUNCDATA [=13=], gclocals·2a5305abe05176240e61b8620e19a815(SB)
0x0000 00000 (elide.go:7) FUNCDATA , 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), -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 , 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 [=18=], gclocals·a36216b97439c93dafebe03e7f0808b5(SB)
0x001d 00029 (elide.go:7) FUNCDATA , 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 [=18=], [=18=]
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 , 8(SP)
0x003e 00062 (elide.go:10) PCDATA [=18=], [=18=]
0x003e 00062 (elide.go:10) CALL runtime.printstring(SB)
0x0043 00067 (elide.go:10) PCDATA [=18=], [=18=]
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 [=18=], $-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
并发访问变量i
需要同步:
synchronization is better done with channels or the facilities of the sync package. Share memory by communicating; don't communicate by sharing memory.
参考: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?