Go: `i+=1` as a only code line in a for loop didn't work normally

Created on 6 Dec 2019  ·  6Comments  ·  Source: golang/go

What version of Go are you using (go version)?

$ go version
go version go1.13.5 linux/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output

$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN="/home/me/go/bin"
GOCACHE="/home/me/.cache/go-build"
GOENV="/home/me/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/me/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build161703294=/tmp/go-build -gno-record-gcc-switches"

What did you do?

1. origin question

run following go script and get different output with and without the comment line

package main

import (
        "fmt"
        "time"
)

func main() {
        var i = 0

        go func() {
                for {
                        i += 1
                //      time.Sleep(1*time.Nanosecond)     <- this comment line
                }
        }()

        for {
                fmt.Println("i is", i)
                time.Sleep(time.Second)
        }
}

with the comment line I get

$ go run main.go
i is 0
i is 3673005
i is 7360961
i is 11182533
i is 14892563
i is 18696280
i is 21821713
....

but strangely, without that comment line I get:

$ go run main.go 
i is 0
i is 0
i is 0
i is 0
i is 0
i is 0
....

2. debug by checking assemble code

with and without the comment line I get different assemble code:
assemble code shown below include the anonymous function run by go routine only.

without that comment line:

....

TEXT main.main.func1(SB) /home/me/go/src/github.com/trainyao/test/main.go
        for {
  0x48d0c0      eb00            JMP 0x48d0c2        
            i += 1
  0x48d0c2      eb00            JMP 0x48d0c4        
  0x48d0c4      eb00            JMP 0x48d0c6        
  0x48d0c6      ebfa            JMP 0x48d0c2    

.....

with that comment line:

...

TEXT main.main.func1(SB) /home/me/go/src/github.com/trainyao/test/main.go
    go func() {
  0x48d0c0      64488b0c25f8ffffff  MOVQ FS:0xfffffff8, CX  
  0x48d0c9      483b6110        CMPQ 0x10(CX), SP   
  0x48d0cd      7636            JBE 0x48d105        
  0x48d0cf      4883ec10        SUBQ $0x10, SP      
  0x48d0d3      48896c2408      MOVQ BP, 0x8(SP)    
  0x48d0d8      488d6c2408      LEAQ 0x8(SP), BP    
        for {
  0x48d0dd      eb00            JMP 0x48d0df        
            i += 1
  0x48d0df      eb00            JMP 0x48d0e1        
  0x48d0e1      488b442418      MOVQ 0x18(SP), AX   
  0x48d0e6      488b00          MOVQ 0(AX), AX      
  0x48d0e9      488b4c2418      MOVQ 0x18(SP), CX   
  0x48d0ee      48ffc0          INCQ AX         
  0x48d0f1      488901          MOVQ AX, 0(CX)      
            time.Sleep(1*time.Nanosecond)
  0x48d0f4      48c7042401000000    MOVQ $0x1, 0(SP)    
  0x48d0fc      e8df71fbff      CALL time.Sleep(SB) 
  0x48d101      eb00            JMP 0x48d103        
            i += 1
  0x48d103      ebda            JMP 0x48d0df        
    go func() {
  0x48d105      e8f646fcff      CALL runtime.morestack_noctxt(SB)   
  0x48d10a      ebb4            JMP main.main.func1(SB)     

...

it seems like go complier ignore that i+=1 when complie into assemble code, and make that i+=1 do nothing.

command I use to generate assemble code:

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build --gcflags "-N -l" -o main main.go
$ go tool objdump -S main > main.S  

What did you expect to see?

output of main.go is the same, with and without that comment line

What did you see instead?

different output

FrozenDueToAge

Most helpful comment

for {
i += 1
}

This is an infinite loop with no synchronization. No other thread is guaranteed to ever see the increment (because that would be a data race, and behavior under data races is undefined), so the compiler optimizes it away.

All 6 comments

This problem also happens on go version go1.13.4 linux/amd64.

package main

import (
    "fmt"
    "time"
)

func main() {
    var i, j int

    noop := func() {}

    onlyAdd := func() {
        fmt.Println("onlyAdd runs")
        for {
            i += 1
        }
    }

    notOnlyAdd := func() {
        fmt.Println("notOnlyAdd runs")
        for {
            j += 1
            noop()
        }
    }

    go onlyAdd()
    go notOnlyAdd()

    for {
        fmt.Println("i is", i, " and j is", j)
        time.Sleep(time.Second)
    }
}

output:

i is 0  and j is 0
onlyAdd runs
notOnlyAdd runs
i is 0  and j is 715125111
i is 0  and j is 1433046851
i is 0  and j is 2154902962
i is 0  and j is 2877082636
i is 0  and j is 3599230418
i is 0  and j is 4319943058
i is 0  and j is 5041545338
i is 0  and j is 5758950518
^C

Same issue on macos on go1.13.4 darwin/amd64.

i is 0  and j is 0
onlyAdd runs
notOnlyAdd runs
i is 0  and j is 800290823
i is 0  and j is 1604651414
i is 0  and j is 2395110243
i is 0  and j is 3187198741
i is 0  and j is 3971968750
i is 0  and j is 4752123974
...

You have a data race. You can see it if you do go run -race main.go, or if you read https://golang.org/doc/articles/race_detector.html.

You should use https://golang.org/wiki/Questions for general questions about why your code doesn't work.

if use go run -race main.go,
it output

i is 53406210
i is 106633590
i is 160145249
i is 213722371
i is 266922550

it looks like the race tool affected the code?

You have a data race. You can see it if you do go run -race main.go, or if you read https://golang.org/doc/articles/race_detector.html.

You should use https://golang.org/wiki/Questions for general questions about why your code doesn't work.

I know there is a data race in the code, but a data race would not make the for loop not adding i variable right? and how to explain the assemble code generated....will data race effect code compile? @mvdan

for {
i += 1
}

This is an infinite loop with no synchronization. No other thread is guaranteed to ever see the increment (because that would be a data race, and behavior under data races is undefined), so the compiler optimizes it away.

Was this page helpful?
0 / 5 - 0 ratings