为什么Go例程之间的共享变量不会更新

时间:2018-03-14 23:05:57

标签: go

为什么下方的代码段会打印出0?但是,i已正确更新为1而没有for循环。

package main

import (
    "fmt"
    "time"
)

func main() {

    var i uint64
    go func() {
        for {
            i++
        }
    }()

    time.Sleep(time.Second)
    fmt.Println(i)
}

解决方案1 ​​

package main

import (
        "fmt"
        "runtime"
        "time"
)

func main() {

        runtime.GOMAXPROCS(1)

        var i uint64
        ch := make(chan bool)
        go func() {
                for {
                        select {
                        case <-ch:
                                return
                        default:
                                i++
                        }
                }
        }()

        time.Sleep(time.Second)
        ch <- true
        fmt.Println(i)
}

解决方案2

package main

import (
        "fmt"
        "runtime"
        "sync"
        "time"
)

func main() {

        runtime.GOMAXPROCS(1)

        var i uint64
        var mx sync.Mutex
        go func() {
                for {
                        mx.Lock()
                        i++
                        mx.Unlock()
                }
        }()

        time.Sleep(time.Second)
        mx.Lock()
        fmt.Println(i)
        mx.Unlock()
}

解决方案3

package main

import (
        "fmt"
        "runtime"
        "sync/atomic"
        "time"
)

func main() {

        runtime.GOMAXPROCS(1)

        var i uint64
        go func() {
                for {
                        atomic.AddUint64(&i, 1)
                        runtime.Gosched()
                }
        }()

        time.Sleep(time.Second)
        fmt.Println(atomic.LoadUint64(&i))
}

4 个答案:

答案 0 :(得分:1)

在go操场上运行它会超时,这让我感到很惊讶,因为正如Tarion所说,无限循环应该在退出main后终止。似乎go操场等待所有goroutines在退出之前完成。我想你看到0的原因是在这里做坏的多线程。 for循环在main读取i的同时不断更新i,结果无法预测。我不确定您要演示的是什么,但是您需要一个互斥锁或一个频道来在不同的线程上安全地读取和更新i

答案 1 :(得分:1)

你有竞争条件。结果未定义。

package main

import (
    "fmt"
    "time"
)

func main() {

    var i uint64
    go func() {
        for {
            i++
        }
    }()

    time.Sleep(time.Second)
    fmt.Println(i)
}

输出:

$ go run -race racer.go
==================
WARNING: DATA RACE
Read at 0x00c0000a2010 by main goroutine:
  main.main()
      /home/peter/src/racer.go:18 +0x95

Previous write at 0x00c0000a2010 by goroutine 6:
  main.main.func1()
      /home/peter/src/racer.go:13 +0x4e

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

参考:Introducing the Go Race Detector

答案 2 :(得分:1)

理解的必要阅读 - The Go Memory Model

  

Go内存模型指定了一个条件,在该条件下,可以保证在一个goroutine中读取变量,以观察通过写入不同goroutine中的同一变量而产生的值。

每当有2个或更多并发goroutine访问相同内存并且其中一个修改内存时(例如,在您的示例中为i++),您需要进行某种同步:mutex或{{3} }。

这样的事情将是代码所需的最小修改:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {

    var (
        mu sync.Mutex
        i  uint64
    )

    go func() {
        for {
            mu.Lock()
            i++
            mu.Unlock()
        }
    }()

    time.Sleep(time.Second)

    mu.Lock()
    fmt.Println(i)
    mu.Unlock()
}

哦,官方channel

  

如果您必须阅读本文档的其余部分以了解您的程序的行为,那么您就太聪明了。 不要聪明。

:)玩得开心

答案 3 :(得分:0)

没有for循环,它没有“正确”更新,只是在打印之前随机更新。

在go playgorund中,结尾是:

process took too long 
Program exited.

请参阅:https://play.golang.org/p/qKd-CdLt3uK

当启动go例程时,调度程序将决定何时在执行go例程和main函数之间切换上下文。此外,当主函数结束时,停止例程停止。

你想要做的是在你的例程和主循环之间进行一些同步,因为两者都在访问共享变量i

此处的最佳做法是使用频道,sync.Mutex或sync.WaitGroup。

你可以为你的for循环添加一些延迟,为主函数放弃一些CPU:

go func() {
    for {
        i++
        time.Sleep(1 * time.Millisecond)
    }
}()

这将在1000左右增加一些值,但它仍然不可靠。