美文网首页
【Go】一个数据竞争问题

【Go】一个数据竞争问题

作者: 如雨随行2020 | 来源:发表于2021-12-05 05:28 被阅读0次

    @[toc]

    一、引入

    观察如下程序

    var val = 0
    
    func main() {
        go add()
        go add()
        time.Sleep(1 * time.Second)
        fmt.Println(val)
    }
    
    func add() {
        for i := 0; i < 100; i++ {
            val++
        }
    }
    
    

    在Java中,如果两个线程同一个变量加100次,那么结果是未知的,但是绝大数情况下val的值是小于200的。但是运行结果却是200(我试了好几次都是这个结果)


    二、解释


    这是修改后的程序以及运行结果,似乎找到了原因。因为add()运行太快了,第一个协程运行完成后第二个协程才开始运行。加上Sleep后,两个协程能交替执行,才会出现数据竞争。

    三、再深一步

    1. 为什么数据竞争这么“严重”
      理论上,5ms对于cpu来说是很长的,两个协程同时修改val概率没有这么大,所以结果在100~200直接,会更靠近200些。
    2. 上面是公司的笔记本运行结果,而在我的台式机上结果还是200,这是为什么

    第一个问题,涉及到内存同步,参考链接:曹政谈重排。两个协程的操作大部分只写到各自的store buffer中,最终内存同步时,才会有大量自增是无效的。
    第二个问题,我开始认为我的电脑是单核的,但是看配置却是


    然后,我怀疑是我的Go环境没有配置正确(使用的默认值)

    GOMAXPROCS:在Go 1.5版本之前,GOMAXPROCS参数的值默认是1

    我没有设置GOMAXPROCS,而Go的版本是1.16,GOMAXPROCS没有设置的话,有几个核就会使用几个核,并且加上如下代码,结果显示并不是单核运行

        num := runtime.NumCPU()
        fmt.Println("使用cpu数量", num) // 使用cpu数量 12
    

    然后,我又怀疑两个协程没有交替运行,于是我又改进了程序,将val改变的中间值,以及哪个协程修改打印了出来

    var val = 0
    
    func main() {
        num := runtime.NumCPU()
        fmt.Println("使用cpu数量", num)
        go add("A")
        go add("B")
        time.Sleep(1 * time.Second)
        fmt.Println("val的最终结果", val)
    }
    
    func add(proc string) {
        for i := 0; i < 100; i++ {
            val++
            fmt.Printf("execute process[%s] and val is %d\n", proc, val)
            time.Sleep(5 * time.Millisecond)
        }
    }
    

    但是结果如下



    证明两个协程是交替运行的,我又迷惑了

    四、结尾

    感谢rustyx的解惑data-race question
    上面程序有两个需要改进的地方

    1. 主协程sleep时间太短了,笔记本上运行结果118可能是因为add()协程没有运行完。
    2. val自增的次数(100)太少了,一个协程先跑完,另一个还没开始运行

    于是,我又改进了代码

    var val = 0
    
    func main() {
        num := runtime.NumCPU()
        fmt.Println("使用cpu数量", num)
        var wg = sync.WaitGroup{}
        wg.Add(2)
        go add("A", &wg)
        go add("B", &wg)
        wg.Wait()
        fmt.Println("val的最终结果", val)
    }
    func add(proc string, wg *sync.WaitGroup) {
        defer wg.Done()
        for i := 0; i < 1000; i++ {
            val++
        }
    }
    

    这是笔记本上运行的一次结果



    下面是台式机上运行的结果



    终于,可以看到发生数据竞争了。当然,更多的运行结果还是2000

    相关文章

      网友评论

          本文标题:【Go】一个数据竞争问题

          本文链接:https://www.haomeiwen.com/subject/lvsraltx.html