package main
import \"time\"
func main() {
i := 1
go func() {
for {
i++
}
}()
<-time.After(1 * time.Second)
prin
Concurrent access to the variable i
need to be synchronized:
synchronization is better done with channels or the facilities of the sync package. Share memory by communicating; don't communicate by sharing memory.
ref: https://golang.org/pkg/sync/atomic/
This is interesting, so I'm sharing my experiments:
0- Your code using time.Sleep(1 * time.Second)
(not recommended-not synchronized):
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
}
}()
time.Sleep(1 * time.Second)
println(i)
}
output:
1
1- Using i := new(int)
(not recommended-not synchronized):
package main
import "time"
func main() {
i := new(int)
go func() {
for {
*i++
}
}()
time.Sleep(1 * time.Second)
println(*i)
}
output(CPU: i7-7700K @ 4.2GHz):
772252413
2- Synchronizing using 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
}
output:
233008800
3- Synchronizing using 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)
}
output:
272702341
4- Synchronizing using 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)
}
output:
261459418
5- Synchronizing using quit channel:
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)
}
output:
277366276
6- Synchronizing using 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
}
output:
24271318
related topics:
The Go Memory Model
There is no equivalent to volatile and register in Go
Does Go support volatile / non-volatile variables?
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\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
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