为什么goroutine无法读取全局var ops值?

时间:2018-07-28 10:41:05

标签: go goroutine

package main

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

func init() {
    runtime.GOMAXPROCS(runtime.NumCPU())
}

func main() {
    var t1 = time.Now()
    var ops uint64 = 0
    go func() {
        for {
            time.Sleep(time.Second)
            opsFinal := atomic.LoadUint64(&ops)
            fmt.Println("ops:", opsFinal, "qps:", opsFinal/uint64(time.Since(t1).Seconds()))
        }
    }()

    for {
        atomic.AddUint64(&ops, 1)
        //runtime.Gosched()
    }
}

在这种情况下,每秒输出“ ops:0 qps:0”,为什么不能在goroutine中读取ops?

但是添加 runtime.Gosched()时,一切正常!

每个人都能帮助我吗?

2 个答案:

答案 0 :(得分:2)

我将更新go版本,请检查,

[mh-cbon@pc3 y] $ go run main.go 
ops: 97465383 qps: 97465383
ops: 195722110 qps: 97861055
ops: 293058057 qps: 97686019
ops: 390971243 qps: 97742810
^Csignal: interrupt
[mh-cbon@pc3 y] $ go version
go version go1.10 linux/amd64
[mh-cbon@pc3 y] $ gvm use 1.8
Now using version go1.8
[mh-cbon@pc3 y] $ go version
go version go1.8 linux/amd64
[mh-cbon@pc3 y] $ go run main.go 
ops: 0 qps: 0
ops: 0 qps: 0
^Csignal: interrupt
[mh-cbon@pc3 y] $ 

答案 1 :(得分:2)

我对The Go Memory Model的理解是,这是对您编写的程序的正确执行:没有保证AddUint64()在主程序之前发生的调用LoadUint64()在goroutine中进行调用,因此在任何写入发生之前对变量的每次读取都是合法的。如果编译器知道"sync/atomic"非常特殊,并且得出结论是增量的结果是不可观察的,那么我就不会感到完全震惊,因此只需删除最终循环即可。

The Go Memory Modelsync/atomic documentation都建议您反对使用的方法。 "sync/atomic"告诫:

  

通过通信共享内存;不要通过共享内存进行通信。

一个更好的程序可能看起来像这样:

package main

import "fmt"
import "time"

func count(op <-chan struct{}) {
    t1 := time.Now()
    ops := 0
    tick := time.Tick(time.Second)
    for {
        select {
        case <-op:
            ops++
        case <-tick:
            dt := time.Since(t1).Seconds()
            fmt.Printf("ops: %d qps: %f\n", ops, float64(ops)/dt)
        }
    }
}

func main() {
    op := make(chan struct{})
    go count(op)
    for {
        op <- struct{}{}
    }
}

请注意,除了通过通道发送的数据外,主程序与goroutine之间没有共享任何状态。