Go 中一个非典型不加锁读写变量案例分析
发表于 由 daniel
前段时间在
v2
看到一个关于并发读写变量的问题:
go 一个线程写, 另外一个线程读, 为什么不能保证最终一致性
。帖子中给出的例子非常简单(稍作修改)
main.go
:
package main
import (
"fmt"
"runtime"
"time"
)
var i = 0
func main() {
runtime.GOMAXPROCS(2)
go func() {
for {
fmt.Println("i am here", i)
time.Sleep(time.Second)
}
}()
for {
i += 1
}
}
既然是问题贴,直接运行的结果应该是出乎大多数人预料的:
╰─➤ go run main.go 1 ↵
i am here 0
i am here 0
i am here 0
i am here 0
i am here 0
i am here 0
...
帖子的回复比较多,涉及的信息量相对杂乱,爬完楼反而感觉没有看懂。这里就不卖关子,直接给出脱水后的结论:
出现上面结果的原因是 go 的编译器把代码 i 自加 1 的 for 循环优化掉了
。要验证这一点也很简单,我们使用
go tool objdump -s 'main\.main' main
查看编译出的二进制可执行文件的汇编代码:
╰─➤ go tool objdump -s 'main\.main' main
TEXT main.main(SB) /Users/liudanking/code/golang/gopath/src/test/main.go
main.go:11 0x108de60 65488b0c25a0080000 MOVQ GS:0x8a0, CX
main.go:11 0x108de69 483b6110 CMPQ 0x10(CX), SP
main.go:11 0x108de6d 7635 JBE 0x108dea4
main.go:11 0x108de6f 4883ec18 SUBQ $0x18, SP
main.go:11 0x108de73 48896c2410 MOVQ BP, 0x10(SP)
main.go:11 0x108de78 488d6c2410 LEAQ 0x10(SP), BP
main.go:12 0x108de7d 48c7042402000000 MOVQ $0x2, 0(SP)
main.go:12 0x108de85 e8366bf7ff CALL runtime.GOMAXPROCS(SB)
main.go:13 0x108de8a c7042400000000 MOVL $0x0, 0(SP)
main.go:13 0x108de91 488d05187f0300 LEAQ go.func.*+115(SB), AX
main.go:13 0x108de98 4889442408 MOVQ AX, 0x8(SP)
main.go:13 0x108de9d e8fe13faff CALL runtime.newproc(SB)
main.go:20 0x108dea2 ebfe JMP 0x108dea2
main.go:11 0x108dea4 e8c7dffbff CALL runtime.morestack_noctxt(SB)
main.go:11 0x108dea9 ebb5 JMP main.main(SB)
:-1 0x108deab cc INT $0x3
:-1 0x108deac cc INT $0x3
:-1 0x108dead cc INT $0x3
:-1 0x108deae cc INT $0x3
:-1 0x108deaf cc INT $0x3
TEXT main.main.func1(SB) /Users/liudanking/code/golang/gopath/src/test/main.go
main.go:13 0x108deb0 65488b0c25a0080000 MOVQ GS:0x8a0, CX
main.go:13 0x108deb9 483b6110 CMPQ 0x10(CX), SP
main.go:13 0x108debd 0f8695000000 JBE 0x108df58
main.go:13 0x108dec3 4883ec58 SUBQ $0x58, SP
main.go:13 0x108dec7 48896c2450 MOVQ BP, 0x50(SP)
main.go:13 0x108decc 488d6c2450 LEAQ 0x50(SP), BP
main.go:15 0x108ded1 0f57c0 XORPS X0, X0
main.go:15 0x108ded4 0f11442430 MOVUPS X0, 0x30(SP)
main.go:15 0x108ded9 0f11442440 MOVUPS X0, 0x40(SP)
main.go:15 0x108dede 488d059b020100 LEAQ runtime.types+65664(SB), AX
main.go:15 0x108dee5 4889442430 MOVQ AX, 0x30(SP)
main.go:15 0x108deea 488d0d0f2d0400 LEAQ main.statictmp_0(SB), CX
main.go:15 0x108def1 48894c2438 MOVQ CX, 0x38(SP)
main.go:15 0x108def6 488d1583fb0000 LEAQ runtime.types+63872(SB), DX
main.go:15 0x108defd 48891424 MOVQ DX, 0(SP)
main.go:15 0x108df01 488d1d107c0c00 LEAQ main.i(SB), BX
main.go:15 0x108df08 48895c2408 MOVQ BX, 0x8(SP)
main.go:15 0x108df0d e84eddf7ff CALL runtime.convT2E64(SB)
main.go:15 0x108df12 488b442410 MOVQ 0x10(SP), AX
main.go:15 0x108df17 488b4c2418 MOVQ 0x18(SP), CX
main.go:15 0x108df1c 4889442440 MOVQ AX, 0x40(SP)
main.go:15 0x108df21 48894c2448 MOVQ CX, 0x48(SP)
main.go:15 0x108df26 488d442430 LEAQ 0x30(SP), AX
main.go:15 0x108df2b 48890424 MOVQ AX, 0(SP)
main.go:15 0x108df2f 48c744240802000000 MOVQ $0x2, 0x8(SP)
main.go:15 0x108df38 48c744241002000000 MOVQ $0x2, 0x10(SP)
main.go:15 0x108df41 e85a9dffff CALL fmt.Println(SB)
main.go:16 0x108df46 48c7042400ca9a3b MOVQ $0x3b9aca00, 0(SP)
main.go:16 0x108df4e e87d27fbff CALL time.Sleep(SB)
main.go:15 0x108df53 e979ffffff JMP 0x108ded1
main.go:13 0x108df58 e813dffbff CALL runtime.morestack_noctxt(SB)
main.go:13 0x108df5d e94effffff JMP main.main.func1(SB)
:-1 0x108df62 cc INT $0x3
:-1 0x108df63 cc INT $0x3
:-1 0x108df64 cc INT $0x3
:-1 0x108df65 cc INT $0x3
:-1 0x108df66 cc INT $0x3
:-1 0x108df67 cc INT $0x3
:-1 0x108df68 cc INT $0x3
:-1 0x108df69 cc INT $0x3
:-1 0x108df6a cc INT $0x3
:-1 0x108df6b cc INT $0x3
:-1 0x108df6c cc INT $0x3
:-1 0x108df6d cc INT $0x3
:-1 0x108df6e cc INT $0x3
:-1 0x108df6f cc INT $0x3
显然,