前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >谈谈 Golang 中的 Data Race(续)

谈谈 Golang 中的 Data Race(续)

作者头像
poslua
发布2019-08-19 14:50:06
1.1K0
发布2019-08-19 14:50:06
举报
文章被收录于专栏:posluaposlua

我在上一篇文章中曾指出:在 Go 的内存模型中,有 race 的 Go 程序的行为是未定义行为,理论上出现什么情况都是正常的。并尝试通过一段有 data race 的代码来说明问题:

代码语言:javascript
复制
package main

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

var i = 0

func main() {
    runtime.GOMAXPROCS(2)

    go func() {
        for {
            fmt.Println("i is", i)
            time.Sleep(time.Second)
        }
    }()

    for {
        i += 1
    }
}

当通过 go run cmd.go 执行时,大概率会得到下面这样的输出:

代码语言:javascript
复制
i is: 0
i is: 0
i is: 0
i is: 0

然而有些同学提到:之所以输出 0 是因为 i+=1 所在的 goroutine 没有新的栈帧创建,因此没有被调度器调度到。解释似乎也合理,但是事实却不是这样的。真实的原因是:编译器把那段自增的 for 循环给全部优化掉了

要验证这一点,我们要先从编译器优化说起。传统的编译器通常分为三个部分,前端(frontEnd),优化器(Optimizer)和后端(backEnd)。在编译过程中,前端主要负责词法和语法分析,将源代码转化为抽象语法树;优化器则是在前端的基础上,对得到的中间代码进行优化,使代码更加高效;后端则是将已经优化的中间代码转化为针对各自平台的机器代码。

go 的编译器也一样,在生成目标代码的时候会做很多优化,重要的有:

  • 指令重排
  • 逃逸分析
  • 函数内联
  • 死码消除

当我们通过:

代码语言:javascript
复制
go build cmd.go
go tool objdump -s main.main cmd

查看编译出的二进制可执行文件的汇编代码:

代码语言:javascript
复制
cmd.go:11        0x4858c0        64488b0c25f8ffffff  MOVQ FS:0xfffffff8, CX
  cmd.go:11        0x4858c9        483b6110        CMPQ 0x10(CX), SP
  cmd.go:11        0x4858cd        7635            JBE 0x485904
  cmd.go:11        0x4858cf        4883ec18        SUBQ $0x18, SP
  cmd.go:11        0x4858d3        48896c2410      MOVQ BP, 0x10(SP)
  cmd.go:11        0x4858d8        488d6c2410      LEAQ 0x10(SP), BP
  cmd.go:12        0x4858dd        48c7042402000000    MOVQ $0x2, 0(SP)
  cmd.go:12        0x4858e5        e83605f8ff      CALL runtime.GOMAXPROCS(SB)
  cmd.go:14        0x4858ea        c7042400000000      MOVL $0x0, 0(SP)
  cmd.go:14        0x4858f1        488d05a8640300      LEAQ 0x364a8(IP), AX
  cmd.go:14        0x4858f8        4889442408      MOVQ AX, 0x8(SP)
  cmd.go:14        0x4858fd        e89eaafaff      CALL runtime.newproc(SB)
  cmd.go:22        0x485902        ebfe            JMP 0x485902
  cmd.go:11        0x485904        e8e79afcff      CALL runtime.morestack_noctxt(SB)
  cmd.go:11        0x485909        ebb5            JMP main.main(SB)

显然,下面这一段直接被优化没了:

代码语言:javascript
复制
for {
    i += 1
}

why? 因为这段代码是有竞态的,没有任何同步机制。go 编译器认为这一段是 dead code,索性直接优化掉了。

而当我们通过 go build-race cmd.go 编译后:

代码语言:javascript
复制
cmd.go:22        0x4d3430        488d05c9211100      LEAQ main.i(SB), AX
  cmd.go:22        0x4d3437        48890424        MOVQ AX, 0(SP)
  cmd.go:22        0x4d343b        e8d096faff      CALL runtime.raceread(SB)
  cmd.go:22        0x4d3440        488b05b9211100      MOVQ main.i(SB), AX
  cmd.go:22        0x4d3447        4889442410      MOVQ AX, 0x10(SP)
  cmd.go:22        0x4d344c        488d0dad211100      LEAQ main.i(SB), CX
  cmd.go:22        0x4d3453        48890c24        MOVQ CX, 0(SP)
  cmd.go:22        0x4d3457        e8f496faff      CALL runtime.racewrite(SB)
  cmd.go:22        0x4d345c        488b442410      MOVQ 0x10(SP), AX
  cmd.go:22        0x4d3461        48ffc0          INCQ AX
  cmd.go:22        0x4d3464        48890595211100      MOVQ AX, main.i(SB)

可以明显看到有 INCQ 指令了,这是因为 -race 选项打开了 data race detector 用来检查这个错误而关闭了相关的编译器优化:

代码语言:javascript
复制
==================
WARNING: DATA RACE
Read at 0x0000005e5600 by goroutine 6:
  main.main.func1()
      /root/gofourge/src/lab/cmd.go:16 +0x63

Previous write at 0x0000005e5600 by main goroutine:
  main.main()
      /root/gofourge/src/lab/cmd.go:22 +0x7b

Goroutine 6 (running) created at:
  main.main()
      /root/gofourge/src/lab/cmd.go:14 +0x4f
==================
i is: 4085
i is: 56001323
i is: 112465799
i is: 168640611

如此,运行结果就“看似正确”了。

最后再引用一句 golang-nuts 上的评论:

Any race is a bug. When there is a race, the compiler is free to do whatever it wants.

参考资料

  • Go compiler - Loop transformations
  • Would this race condition be considered a bug?
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 poslua 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考资料
相关产品与服务
命令行工具
腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档