go 一个线程写, 另外一个线程读, 为什么不能保证最终一致性?

2018-06-28 15:29:32 +08:00
 xiadada
package main

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

var lock sync.RWMutex
var i = 0

func main() {
    runtime.GOMAXPROCS(2)
    go func() {
        for {
            fmt.Println("i am here", i)
            time.Sleep(time.Second)
        }
    }()
    for {
        i += 1
    }
}

结果始终是 0, 考虑 cpu cache 一致性的话, 过一段时间就会看到变量发生了变化啊?

6300 次点击
所在节点    Go 编程语言
42 条回复
Monad
2018-06-28 15:32:45 +08:00
编译器是可以优化的, 并且没有 memory barrier
wkc
2018-06-28 15:41:54 +08:00
`go run -race a.go` 就能看到预期变化了
xiadada
2018-06-28 15:49:03 +08:00
@wkc 确实,还没这么用过
xiadada
2018-06-28 15:50:03 +08:00
@Monad 我看了一下汇编代码, 确实是被编译器编译掉了, 他可能认为`i`没有变化, 根本没有 Add 过.
zsxzy
2018-06-28 16:09:14 +08:00
没有 volatile , 这个怎么解决
xiadada
2018-06-28 16:16:17 +08:00
@Monad 再请教一下啊, 内存屏障和 cache 一致性有关系吗? 网上说写屏障会发消息让其他 cache 失效, 如果不设置屏障, 难道不会达成最终一致性吗?
xiadada
2018-06-28 16:16:50 +08:00
@zsxzy go 用更上一层的读写锁
Monad
2018-06-28 16:20:52 +08:00
@xiadada #6 在 i386 和 x86_64 上应该是会的 其它架构我就不清楚了 这里的主要原因其实还是没有屏障导致编译器优化掉了
polythene
2018-06-28 16:22:00 +08:00
@wkc 请教一下,这里的“-race ”做了什么神奇的操作导致 i 发生了变化?
rrfeng
2018-06-28 16:31:48 +08:00
看不懂……有没有人详细解释一下
lostsquirrelX
2018-06-28 16:50:45 +08:00
按按 go tour 的说法有两种方式
一种是 chan
一种是 把你的变量和锁放在一个结构体里面
finalsatan
2018-06-28 18:52:44 +08:00
seaswalker
2018-06-28 18:58:49 +08:00
需要一个 compiler 屏障就行了呗
scnace
2018-06-28 19:06:23 +08:00
没看懂这个 lock 定义在这干啥。。。
WildCat
2018-06-28 19:08:23 +08:00
@scnace +1 同样没看懂
CRVV
2018-06-28 19:11:47 +08:00
@polythene
楼主发的代码包含 data race,-race 打开了 data race detector,用来检查这个错误,为了检查错误关了相关的编译器优化

@lostsquirrelX
变量和锁不用放在一个结构体里,随便怎么放都行

曾经有一个和这事相关的 bug
https://github.com/golang/go/issues/19182
scnace
2018-06-28 19:13:16 +08:00
https://golang.org/doc/articles/race_detector.html 其实 Go 文档还是挺详细的。。。
xfriday
2018-06-28 21:23:57 +08:00
在 i +=1 下面添加一行 runtime.Gosched() 结果就是你期望的
gabon
2018-06-28 23:15:36 +08:00
volatile
yangxin0
2018-06-28 23:33:18 +08:00
这个要从 memory model 说起。i += 1 其实是两个指令:
mov i, %eax
add %eax, 1
所以当你在 for { i += 1}的时候存在两个 instructions, 而另一个读取 print i 的时候可能在 mov 之后也可能在 add 之后。所以你这个一致性要是不增加 memory fence 基本无解。

解法有几种:
1、原子 add
2、chan 传递数据
3、mutex 或者 rwlock

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/466586

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX