从goroutine更改变量

时间:2018-07-11 14:15:10

标签: go goroutine

我试图用2个goroutines编写示例程序来说明比赛条件。一次打印x,另一次打印x

package main

import (
    "fmt"
    "time"
)

func main() {
    x := 0

    go func() {
        for {
            if x%2 == 0 {
                fmt.Println(x)
            }
        }
    }()

    go func() {
        for {
            x = x + 1
        }
    }()

    <-time.After(1 * time.Second)
    fmt.Println("final", x)
}

但是增量例程什么也不做,最后x保持为零。但是,如果递增例程添加fmt.Println(x),它将开始递增x并按预期运行程序。

更新#1:

此简单示例的目的仅是说明数据争用问题。我知道在不同步的情况下读取两个goroutine之间的共享变量是一个坏主意。请停下来向我指出。我的问题是为什么增量goroutine在这里什么都不做?在我的世界图中,应该稳定增加,但是问题应该出现在无法确定结果的读取程序中,因为读取x是一个,而打印x时可能是另一个。

更新#2:

我有另一个例子:

package main

import (
    "time"
    "fmt"
)

func main() {
    x := 0

    go func() {
        last := x
        for {
            current := x
            if last != current {
                last = current
                fmt.Println(last)
            }
        }
    }()

    go func() {
        for {
            //<-time.After(1 * time.Second) // if uncomment this row program start do "something"
            x = x + 1
        }
    }()

    <-time.After(10 * time.Second)
    fmt.Println("final", x)
}

该示例非常简单。一个例行增量计数器,如果更改则另打印。仅当写入x = x+1并读取current := x时,该程序才具有未定义的行为,它不是原子操作,并且一个线程在写入过程中开始在另一个线程中读取变量,而另一个操作则读取已损坏值。 但是,为什么在这种情况下,当增量例程程序中的非注释睡眠开始起作用时?

2 个答案:

答案 0 :(得分:0)

您有数据竞争:Go Data Race Detector。因此,您的结果是不确定的。


package main

import (
    "fmt"
    "time"
)

func main() {
    x := 0

    go func() {
        for {
            if x%2 == 0 {
                //fmt.Println(x)
            }
        }
    }()

    go func() {
        for {
            x = x + 1
        }
    }()

    <-time.After(1 * time.Second)
    fmt.Println("final", x)
}

输出:

$ go run -race racer.go > /dev/null
==================
WARNING: DATA RACE
Write at 0x00c000088010 by goroutine 7:
  main.main.func2()
      /home/peter/gopath/src/racer.go:21 +0x4e

Previous read at 0x00c000088010 by goroutine 6:
  main.main.func1()
      /home/peter/gopath/src/racer.go:13 +0x38

Goroutine 7 (running) created at:
  main.main()
      /home/peter/gopath/src/racer.go:19 +0x9c

Goroutine 6 (running) created at:
  main.main()
      /home/peter/gopath/src/racer.go:11 +0x7a
==================
Found 1 data race(s)
exit status 66
$ 

package main

import (
    "fmt"
    "time"
)

func main() {
    x := 0

    go func() {
        for {
            if x%2 == 0 {
                fmt.Println(x)
            }
        }
    }()

    go func() {
        for {
            x = x + 1
        }
    }()

    <-time.After(1 * time.Second)
    fmt.Println("final", x)
}

输出:

$ go run -race racer.go > /dev/null
==================
WARNING: DATA RACE
Read at 0x00c00008c010 by goroutine 6:
  main.main.func1()
      /home/peter/gopath/src/racer.go:13 +0x3c

Previous write at 0x00c00008c010 by goroutine 7:
  main.main.func2()
      /home/peter/gopath/src/racer.go:21 +0x4e

Goroutine 6 (running) created at:
  main.main()
      /home/peter/gopath/src/racer.go:11 +0x7a

Goroutine 7 (running) created at:
  main.main()
      /home/peter/gopath/src/racer.go:19 +0x9c
==================
==================
WARNING: DATA RACE
Write at 0x00c00008c010 by goroutine 7:
  main.main.func2()
      /home/peter/gopath/src/racer.go:21 +0x4e

Previous read at 0x00c00008c010 by goroutine 6:
  main.main.func1()
      /home/peter/gopath/src/racer.go:13 +0x3c

Goroutine 7 (running) created at:
  main.main()
      /home/peter/gopath/src/racer.go:19 +0x9c

Goroutine 6 (running) created at:
  main.main()
      /home/peter/gopath/src/racer.go:11 +0x7a
==================
^Csignal: interrupt
$ 

答案 1 :(得分:0)

您看不到任何事情发生,因为编译器很聪明并且可以优化增量。因为没有同步,所以编译器可以有效地看到以下代码:

x := 0
for { x = x + 1 }

由于x从未在任何地方真正读取(仅作为增量的一部分),因此允许编译器将多个增量合并为一个。例如:

x := 0
for { x += 10 }

或者也许:

x := 0
for { x += 1<<64 }

与以下相同:

x := 0
for { x = 0 }

这显然是没有意义的,因此编译器会优化整个循环。