https://mp.weixin.qq.com/s/pVJiFdDDKVx707eKL19bjA

谈谈 Golang 中的 Data Race

原创 ms2008 poslua 2019-05-13

Any race is a bug

我在接手其他同事的 golang 项目时,一般都会习惯性的做一个竞态检测。有时总会得到一些“惊喜”,比如像下面这段代码:

 
  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "runtime"
  6. "time"
  7. )
  8.  
  9. var i = 0
  10.  
  11. func main() {
  12. runtime.GOMAXPROCS(2)
  13. go func() {
  14. for {
  15. fmt.Println("i is ", i)
  16. time.Sleep(time.Second)
  17. }
  18. }()
  19. for {
  20. i += 1
  21. }
  22. }

  

当通过 go run-race cmd.go 执行时,可以看到有明显的竞态出现:

  1.  
  1. go run -race g.go
  2. ==================
  3. WARNING: DATA RACE
  4. Read at 0x0000006459c0 by goroutine 7:
  5. main.main.func1()
  6. /home/s/goAction/tDataRace/g.go:15 +0x3e
  7.  
  8. Previous write at 0x0000006459c0 by main goroutine:
  9. main.main()
  10. /home/s/goAction/tDataRace/g.go:20 +0x84
  11.  
  12. Goroutine 7 (running) created at:
  13. main.main()
  14. /home/s/goAction/tDataRace/g.go:13 +0x53
  15. ==================
  16. i is 380899
  17. i is 33836429

  

我觉得不同的 goroutine 并发读写同一个变量,需要加锁,这应该是天经地义的常识。但是总有人以为,不加锁导致的问题最多就是读取的数据是修改前的数据,不能保证原子性罢了。是这样的吗?从上面的输出来看,似乎也差不多,其实这些都是典型的误解。

有些朋友可能不知道,在 Go(甚至是大部分语言)中,一条普通的赋值语句其实并不是一个原子操作(语言规范同样没有定义 i++ 是原子操作, 任何变量的赋值都不是原子操作)。例如,在 32 位机器上写 int64类型的变量是有中间状态的,它会被拆成两次写操作 MOV —— 写低 32 位和写高 32 位,如下图所示:

如果一个线程刚写完低 32 位,还没来得及写高 32 位时,另一个线程读取了这个变量,那它得到的就是一个毫无逻辑的中间变量,这很有可能使我们的程序出现诡异的 Bug。

而在 Go 的内存模型中,有 race 的 Go 程序的行为是未定义行为,理论上出现什么情况都是正常的。就拿上面的代码来说,当去掉 -race 参数执行时,大概率会得到这样的输出:

  1. i is: 0

  2. i is: 0

  3. i is: 0

  4. i is: 0

而用较老的 go 版本执行时,基本上执行一段时间,程序就会 HANG 住。所以讨论为什么出现这种现象实际上没有任何意义,不要依赖这种行为。

Mutex vs Atomic

解决 race 的问题时,无非就是上锁。可能很多人都听说过一个高逼格的词叫「无锁队列」。 都一听到加锁就觉得很 low,那无锁又是怎么一回事?其实就是利用 atomic 特性,那 atomic 会比 mutex 有什么好处呢?Benign Data Races: What Could Possibly Go Wrong? 的作者总结了这两者的一个区别:

Mutexes do no scale. Atomic loads do.

mutex操作系统实现,而 atomic 包中的原子操作则由底层硬件直接提供支持。在 CPU 实现的指令集里,有一些指令被封装进了 atomic 包,这些指令在执行的过程中是不允许中断(interrupt)的,因此原子操作可以在 lock-free 的情况下保证并发安全,并且它的性能也能做到随 CPU 个数的增多而线性扩展。

若实现相同的功能,后者通常会更有效率,并且更能利用计算机多核的优势。所以,以后当我们想并发安全的更新一些变量的时候,我们应该优先选择用 atomic 来实现。

参考资料

  • The Go Memory Model

  • Would this race condition be considered a bug?

  • 理解Go标准库中的atomic.Value类型

  1. [s@s tDataRace]$ ll -as
  2. total 2000
  3. 4 drwxr-xr-x 2 s go 4096 Dec 21 08:11 .
  4. 4 drwxr-xr-x 13 s go 4096 Dec 21 08:03 ..
  5. 1988 -rwxr-xr-x 1 s go 2035035 Dec 21 08:11 g
  6. 4 -rw-r--r-- 1 s go 200 Dec 21 08:05 g.go
  7. [s@s tDataRace]$ rm -rf g
  8. [s@s tDataRace]$ go build g.go
  9. [s@s tDataRace]$ ll -as
  10. total 2000
  11. 4 drwxr-xr-x 2 s go 4096 Dec 21 08:11 .
  12. 4 drwxr-xr-x 13 s go 4096 Dec 21 08:03 ..
  13. 1988 -rwxr-xr-x 1 s go 2035035 Dec 21 08:11 g
  14. 4 -rw-r--r-- 1 s go 200 Dec 21 08:05 g.go
  15. [s@s tDataRace]$ go tool objdump -s main.main g
  16. TEXT main.main(SB) /home/s/goAction/tDataRace/g.go
  17. g.go:11 0x4992e0 64488b0c25f8ffffff MOVQ FS: 0xfffffff8, CX
  18. g.go:11 0x4992e9 483b6110 CMPQ 0x1 0(CX), SP
  19. g.go:11 0x4992ed 7639 JBE 0x49 9328
  20. g.go:11 0x4992ef 4883ec18 SUBQ $0x 18, SP
  21. g.go:11 0x4992f3 48896c2410 MOVQ BP, 0x10(SP)
  22. g.go:11 0x4992f8 488d6c2410 LEAQ 0x1 0(SP), BP
  23. g.go:12 0x4992fd 48c7042402000000 MOVQ $0x 2, 0(SP)
  24. g.go:12 0x499305 e876d3f6ff CALL run time.GOMAXPROCS(SB)
  25. g.go:13 0x49930a c7042400000000 MOVL $0x 0, 0(SP)
  26. g.go:13 0x499311 488d0598b70200 LEAQ 0x2 b798(IP), AX
  27. g.go:13 0x499318 4889442408 MOVQ AX, 0x8(SP)
  28. g.go:13 0x49931d 0f1f00 NOPL 0(A X)
  29. g.go:13 0x499320 e83b3afaff CALL run time.newproc(SB)
  30. g.go:19 0x499325 90 NOPL
  31. g.go:1 0x499326 ebfd JMP 0x49 9325
  32. g.go:11 0x499328 e87386fcff CALL run time.morestack_noctxt(SB)
  33. g.go:11 0x49932d ebb1 JMP main .main(SB)
  34.  
  35. TEXT main.main.func1(SB) /home/s/goAction/tDataRace/g.go
  36. g.go:13 0x499340 64488b0c25f8ffffff MOVQ FS: 0xfffffff8, CX
  37. g.go:13 0x499349 483b6110 CMPQ 0x1 0(CX), SP
  38. g.go:13 0x49934d 0f86a4000000 JBE 0x49 93f7
  39. g.go:13 0x499353 4883ec68 SUBQ $0x 68, SP
  40. g.go:13 0x499357 48896c2460 MOVQ BP, 0x60(SP)
  41. g.go:13 0x49935c 488d6c2460 LEAQ 0x6 0(SP), BP
  42. g.go:15 0x499361 488b0598af0e00 MOVQ mai n.i(SB), AX
  43. g.go:15 0x499368 48890424 MOVQ AX, 0(SP)
  44. g.go:15 0x49936c e86f0df7ff CALL run time.convT64(SB)
  45. g.go:15 0x499371 488b442408 MOVQ 0x8 (SP), AX
  46. g.go:15 0x499376 0f57c0 XORPS X0 , X0
  47. g.go:15 0x499379 0f11442440 MOVUPS X 0, 0x40(SP)
  48. g.go:15 0x49937e 0f11442450 MOVUPS X 0, 0x50(SP)
  49. g.go:15 0x499383 488d0d16b50000 LEAQ 0xb 516(IP), CX
  50. g.go:15 0x49938a 48894c2440 MOVQ CX, 0x40(SP)
  51. g.go:15 0x49938f 488d157a170400 LEAQ 0x4 177a(IP), DX
  52. g.go:15 0x499396 4889542448 MOVQ DX, 0x48(SP)
  53. g.go:15 0x49939b 488d1d7eae0000 LEAQ 0xa e7e(IP), BX
  54. g.go:15 0x4993a2 48895c2450 MOVQ BX, 0x50(SP)
  55. g.go:15 0x4993a7 4889442458 MOVQ AX, 0x58(SP)
  56. print.go:274 0x4993ac 488b05fdb50b00 MOVQ os. Stdout(SB), AX
  57. print.go:274 0x4993b3 488d35662d0400 LEAQ go. itab.*os.File,io.Writer(SB), SI
  58. print.go:274 0x4993ba 48893424 MOVQ SI, 0(SP)
  59. print.go:274 0x4993be 4889442408 MOVQ AX, 0x8(SP)
  60. print.go:274 0x4993c3 488d442440 LEAQ 0x4 0(SP), AX
  61. print.go:274 0x4993c8 4889442410 MOVQ AX, 0x10(SP)
  62. print.go:274 0x4993cd 48c744241802000000 MOVQ $0x 2, 0x18(SP)
  63. print.go:274 0x4993d6 48c744242002000000 MOVQ $0x 2, 0x20(SP)
  64. print.go:274 0x4993df 90 NOPL
  65. print.go:274 0x4993e0 e85b9affff CALL fmt .Fprintln(SB)
  66. g.go:16 0x4993e5 48c7042400ca9a3b MOVQ $0x 3b9aca00, 0(SP)
  67. g.go:16 0x4993ed e86e72fcff CALL tim e.Sleep(SB)
  68. g.go:15 0x4993f2 e96affffff JMP 0x49 9361
  69. g.go:13 0x4993f7 e8a485fcff CALL run time.morestack_noctxt(SB)
  70. g.go:13 0x4993fc 0f1f4000 NOPL 0(A X)
  71. g.go:13 0x499400 e93bffffff JMP main .main.func1(SB)
  72. [s@s tDataRace]$ go build -race g.go
  73. [s@s tDataRace]$ go tool objdump -s main.main g
  74. TEXT main.main(SB) /home/s/goAction/tDataRace/g.go
  75. g.go:11 0x512ba0 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX
  76. g.go:11 0x512ba9 483b6110 CMPQ 0x10(CX), SP
  77. g.go:11 0x512bad 0f8683000000 JBE 0x512c36
  78. g.go:11 0x512bb3 4883ec20 SUBQ $0x20, SP
  79. g.go:11 0x512bb7 48896c2418 MOVQ BP, 0x18(SP)
  80. g.go:11 0x512bbc 488d6c2418 LEAQ 0x18(SP), BP
  81. g.go:11 0x512bc1 488b442420 MOVQ 0x20(SP), AX
  82. g.go:11 0x512bc6 48890424 MOVQ AX, 0(SP)
  83. g.go:11 0x512bca e8315cf8ff CALL runtime.racefuncenter(SB)
  84. g.go:12 0x512bcf 48c7042402000000 MOVQ $0x2, 0(SP)
  85. g.go:12 0x512bd7 e8e454f2ff CALL runtime.GOMAXPROCS(SB)
  86. g.go:13 0x512bdc c7042400000000 MOVL $0x0, 0(SP)
  87. g.go:13 0x512be3 488d050e310300 LEAQ 0x3310e(IP), AX
  88. g.go:13 0x512bea 4889442408 MOVQ AX, 0x8(SP)
  89. g.go:13 0x512bef e88cc2f5ff CALL runtime.newproc(SB)
  90. g.go:20 0x512bf4 488d05c52d1300 LEAQ main.i(SB), AX
  91. g.go:20 0x512bfb 48890424 MOVQ AX, 0(SP)
  92. g.go:20 0x512bff 90 NOPL
  93. g.go:20 0x512c00 e8db5af8ff CALL runtime.raceread(SB)
  94. g.go:20 0x512c05 488b05b42d1300 MOVQ main.i(SB), AX
  95. g.go:20 0x512c0c 4889442410 MOVQ AX, 0x10(SP)
  96. g.go:20 0x512c11 488d0da82d1300 LEAQ main.i(SB), CX
  97. g.go:20 0x512c18 48890c24 MOVQ CX, 0(SP)
  98. g.go:20 0x512c1c 0f1f4000 NOPL 0(AX)
  99. g.go:20 0x512c20 e8fb5af8ff CALL runtime.racewrite(SB)
  100. g.go:20 0x512c25 488b442410 MOVQ 0x10(SP), AX
  101. g.go:20 0x512c2a 48ffc0 INCQ AX
  102. g.go:20 0x512c2d 4889058c2d1300 MOVQ AX, main.i(SB)
  103. g.go:20 0x512c34 ebbe JMP 0x512bf4
  104. g.go:11 0x512c36 e8a526f8ff CALL runtime.morestack_noctxt(SB)
  105. g.go:11 0x512c3b 0f1f440000 NOPL 0(AX)(AX*1)
  106. g.go:11 0x512c40 e95bffffff JMP main.main(SB)
  107.  
  108. TEXT main.main.func1(SB) /home/s/goAction/tDataRace/g.go
  109. g.go:13 0x512c60 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX
  110. g.go:13 0x512c69 483b6110 CMPQ 0x10(CX), SP
  111. g.go:13 0x512c6d 0f86d2000000 JBE 0x512d45
  112. g.go:13 0x512c73 4883ec68 SUBQ $0x68, SP
  113. g.go:13 0x512c77 48896c2460 MOVQ BP, 0x60(SP)
  114. g.go:13 0x512c7c 488d6c2460 LEAQ 0x60(SP), BP
  115. g.go:13 0x512c81 488b442468 MOVQ 0x68(SP), AX
  116. g.go:13 0x512c86 48890424 MOVQ AX, 0(SP)
  117. g.go:13 0x512c8a e8715bf8ff CALL runtime.racefuncenter(SB)
  118. g.go:15 0x512c8f 488d052a2d1300 LEAQ main.i(SB), AX
  119. g.go:15 0x512c96 48890424 MOVQ AX, 0(SP)
  120. g.go:15 0x512c9a e8415af8ff CALL runtime.raceread(SB)
  121. g.go:15 0x512c9f 488b051a2d1300 MOVQ main.i(SB), AX
  122. g.go:15 0x512ca6 48890424 MOVQ AX, 0(SP)
  123. g.go:15 0x512caa e8b18ef2ff CALL runtime.convT64(SB)
  124. g.go:15 0x512caf 488b442408 MOVQ 0x8(SP), AX
  125. g.go:15 0x512cb4 0f57c0 XORPS X0, X0
  126. g.go:15 0x512cb7 0f11442440 MOVUPS X0, 0x40(SP)
  127. g.go:15 0x512cbc 0f11442450 MOVUPS X0, 0x50(SP)
  128. g.go:15 0x512cc1 488d0d58c40000 LEAQ 0xc458(IP), CX
  129. g.go:15 0x512cc8 48894c2440 MOVQ CX, 0x40(SP)
  130. g.go:15 0x512ccd 488d151ce20400 LEAQ 0x4e21c(IP), DX
  131. g.go:15 0x512cd4 4889542448 MOVQ DX, 0x48(SP)
  132. g.go:15 0x512cd9 488d1d40bd0000 LEAQ 0xbd40(IP), BX
  133. g.go:15 0x512ce0 48895c2450 MOVQ BX, 0x50(SP)
  134. g.go:15 0x512ce5 4889442458 MOVQ AX, 0x58(SP)
  135. print.go:274 0x512cea 488d057f331000 LEAQ os.Stdout(SB), AX
  136. print.go:274 0x512cf1 48890424 MOVQ AX, 0(SP)
  137. print.go:274 0x512cf5 e8e659f8ff CALL runtime.raceread(SB)
  138. print.go:274 0x512cfa 488b056f331000 MOVQ os.Stdout(SB), AX
  139. print.go:274 0x512d01 488d0db8fa0400 LEAQ go.itab.*os.File,io.Writer(SB), CX
  140. print.go:274 0x512d08 48890c24 MOVQ CX, 0(SP)
  141. print.go:274 0x512d0c 4889442408 MOVQ AX, 0x8(SP)
  142. print.go:274 0x512d11 488d442440 LEAQ 0x40(SP), AX
  143. print.go:274 0x512d16 4889442410 MOVQ AX, 0x10(SP)
  144. print.go:274 0x512d1b 48c744241802000000 MOVQ $0x2, 0x18(SP)
  145. print.go:274 0x512d24 48c744242002000000 MOVQ $0x2, 0x20(SP)
  146. print.go:274 0x512d2d e88e60ffff CALL fmt.Fprintln(SB)
  147. g.go:16 0x512d32 48c7042400ca9a3b MOVQ $0x3b9aca00, 0(SP)
  148. g.go:16 0x512d3a e8a111f8ff CALL time.Sleep(SB)
  149. g.go:16 0x512d3f 90 NOPL
  150. g.go:15 0x512d40 e94affffff JMP 0x512c8f
  151. g.go:13 0x512d45 e89625f8ff CALL runtime.morestack_noctxt(SB)
  152. g.go:13 0x512d4a e911ffffff JMP main.main.func1(SB)
  153. [s@s tDataRace]$

  

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

  1. i is: 0

  2. i is: 0

  3. i is: 0

  4. i is: 0

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

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

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

  • 指令重排

  • 逃逸分析

  • 函数内联

  • 死码消除

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

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

  1. for {

  2. i += 1

  3. }

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

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

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

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

最后再引用一句 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?

Spin-Locks vs. Atomic Instructions - Intel Community https://community.intel.com/t5/Intel-oneAPI-Threading-Building/Spin-Locks-vs-Atomic-Instructions/td-p/894992

c++ - Why is std::mutex faster than std::atomic? - Stack Overflow https://stackoverflow.com/questions/29533755/why-is-stdmutex-faster-than-stdatomic

Benign Data Races: What Could Possibly Go Wrong? https://software.intel.com/content/www/us/en/develop/blogs/benign-data-races-what-could-possibly-go-wrong.html

Comparing the performance of atomic, spinlock and mutex https://demin.ws/blog/english/2012/05/05/atomic-spinlock-mutex/

https://tour.go-zh.org/concurrency/9

sync.Mutex

我们已经看到信道非常适合在各个 Go 程间进行通信。

但是如果我们并不需要通信呢?比如说,若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?

这里涉及的概念叫做 *互斥(mutual*exclusion)* ,我们通常使用 *互斥锁(Mutex)* 这一数据结构来提供这种机制。

Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:

  • Lock
  • Unlock

我们可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行。参见 Inc 方法。

我们也可以用 defer 语句来保证互斥锁一定会被解锁。参见 Value 方法。

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "sync"
  6. "time"
  7. )
  8.  
  9. // SafeCounter 的并发使用是安全的。
  10. type SafeCounter struct {
  11. v map[string]int
  12. mux sync.Mutex
  13. }
  14.  
  15. // Inc 增加给定 key 的计数器的值。
  16. func (c *SafeCounter) Inc(key string) {
  17. c.mux.Lock()
  18. // Lock 之后同一时刻只有一个 goroutine 能访问 c.v
  19. c.v[key]++
  20. c.mux.Unlock()
  21. }
  22.  
  23. // Value 返回给定 key 的计数器的当前值。
  24. func (c *SafeCounter) Value(key string) int {
  25. c.mux.Lock()
  26. // Lock 之后同一时刻只有一个 goroutine 能访问 c.v
  27. defer c.mux.Unlock()
  28. return c.v[key]
  29. }
  30.  
  31. func main() {
  32. c := SafeCounter{v: make(map[string]int)}
  33. for i := 0; i < 1000; i++ {
  34. go c.Inc("somekey")
  35. }
  36.  
  37. time.Sleep(time.Second)
  38. fmt.Println(c.Value("somekey"))
  39. }

  

  1. // 2021/1/4 20:50 Shawn
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "time"
  7. )
  8.  
  9. type UnSafeCounter struct {
  10. v map[string]int
  11. }
  12.  
  13. func (c *UnSafeCounter) Inc(key string) {
  14. c.v[key]++
  15. time.Sleep(10 * time.Millisecond)
  16. }
  17. func (c *UnSafeCounter) Value(key string) int {
  18. return c.v[key]
  19. }
  20.  
  21. func main() {
  22. c := UnSafeCounter{v: make(map[string]int)}
  23. for i := 0; i < 1000; i++ {
  24. go c.Inc("somekey")
  25. }
  26. time.Sleep(time.Second)
  27. fmt.Println(c.Value("somekey"))
  28. }

  

go run -race g.go
1000

go run -race g.go
==================
WARNING: DATA RACE
Read at 0x00c000078150 by goroutine 778:
runtime.mapaccess1_faststr()
/home/shawn/go/src/runtime/map_faststr.go:12 +0x0
main.(*UnSafeCounter).Inc()
/home/shawn/gokit/goAction/tMutex2/g.go:14 +0x74

Previous write at 0x00c000078150 by goroutine 1005:
runtime.mapassign_faststr()
/home/shawn/go/src/runtime/map_faststr.go:202 +0x0
main.(*UnSafeCounter).Inc()
/home/shawn/gokit/goAction/tMutex2/g.go:14 +0xc4

Goroutine 778 (running) created at:
main.main()
/home/shawn/gokit/goAction/tMutex2/g.go:24 +0xb9

Goroutine 1005 (running) created at:
main.main()
/home/shawn/gokit/goAction/tMutex2/g.go:24 +0xb9
==================
==================
WARNING: DATA RACE
Read at 0x00c00007a978 by goroutine 778:
main.(*UnSafeCounter).Inc()
/home/shawn/gokit/goAction/tMutex2/g.go:14 +0x87

Previous write at 0x00c00007a978 by goroutine 1005:
main.(*UnSafeCounter).Inc()
/home/shawn/gokit/goAction/tMutex2/g.go:14 +0xd9

Goroutine 778 (running) created at:
main.main()
/home/shawn/gokit/goAction/tMutex2/g.go:24 +0xb9

Goroutine 1005 (running) created at:
main.main()
/home/shawn/gokit/goAction/tMutex2/g.go:24 +0xb9
==================
1000
Found 2 data race(s)
exit status 66

Any race is a bug. When there is a race, the compiler is free to do whatever it wants.的更多相关文章

  1. 33 Introducing the Go Race Detector

    Introducing the Go Race Detector 26 June 2013 Introduction Race conditions are among the most insidi ...

  2. 竞态条件 race condition data race

    竞态条件 race condition Race condition - Wikipedia https://en.wikipedia.org/wiki/Race_condition A race c ...

  3. 我好像发现了一个Go的Bug?

    从一次重构说起 这事儿还得从一次重构优化说起. 最近在重构一个路由功能,由于路由比较复杂,需求变化也多,于是想通过责任链模式来重构,刚好这段时间也在 Sentinel-Go 中看到相关源码. 用责任链 ...

  4. golang中的race检测

    golang中的race检测 由于golang中的go是非常方便的,加上函数又非常容易隐藏go. 所以很多时候,当我们写出一个程序的时候,我们并不知道这个程序在并发情况下会不会出现什么问题. 所以在本 ...

  5. 28 Data Race Detector 数据种类探测器:数据种类探测器手册

    Data Race Detector 数据种类探测器:数据种类探测器手册 Introduction Usage Report Format Options Excluding Tests How To ...

  6. detect data races The cost of race detection varies by program, but for a typical program, memory usage may increase by 5-10x and execution time by 2-20x.

    小结: 1. conflicting access 2.性能危害 优化 The cost of race detection varies by program, but for a typical ...

  7. 不明显的多线程编程的具体Bugs

    我们都知道,在编写多线程程序时,我们应该记住很多细节,比如锁,使用线程安全库等.这里有一个不太明显的bug的列表,特定于多线程程序.其中许多都没有在初学者的文档或教程中提到,但我认为每个使用线程的人最 ...

  8. C++ 中 ZeroMemory、memset 危险需慎用

    使用C/C++编程时,常使用ZeroMemory.memset或 “={0}”来对结构体对象进行初始化或清零.然而这三种方式都有各自的特点,使用时需谨慎,否则容易出现严重错误,本人今日解决一个导致宕机 ...

  9. ZooKeeper是什么?

    What is ZooKeeper? (译:什么是ZooKeeper?) ZooKeeper is a centralized service for maintaining configuratio ...

随机推荐

  1. 类818tu.c微信小说分销系统设计之定时模板消息源码

    近期将出个系列讲解开发过程,同时作为此系统的开发记录吧,万能的博客园,本边讲解如何发送模板消息,并且能够定时发送,下一篇讲解如何处理多个公众号的网页授权登录问题 [后台]http://xiaoshuo ...

  2. IntelliJ IDEA无法新建类解决办法

    IntelliJ IDEA无法新建类解决办法 灿夏 2018-07-14 08:50:05  4891  收藏 1 展开 原文地址 IntelliJ IDEA使用教程 (总目录篇) [原文地址](ht ...

  3. java中自定义一个异常类 在某些情况抛出自定的异常 ----------阻断程序

    //=============定义异常类 package org.springblade.flow.engine.errorException; /** * 自定义异常处理写入sap失败 */ pub ...

  4. Scriptable Render Pipeline

    Scriptable Render Pipeline SRP的核心是一堆API集合,使得整个渲染过程及相关配置暴露给用户,使得用户可以精确地控制项目的渲染流程. SRP API为原有的Unity构件提 ...

  5. 基于http的netty demo

    1.引入netty的pom <dependency> <groupId>io.netty</groupId> <artifactId>netty-all ...

  6. 当会打王者荣耀的AI学会踢足球,一不小心拿下世界冠军!

    难得的元旦小假期,没有什么比得上在慵懒的冬日艳阳下放松自己,拿起手机,叫上了许久未一起作战的小伙伴,到王者荣耀中激战了一番,仿佛又回到了当年那个年轻的自己. 厉害不,毕竟当年DD也是王者五十星的水平, ...

  7. 万字概览 Java 虚拟机

    为什么要学习 JVM 在很多 Java 程序员的开发生涯里,JVM 一直是黑盒子一般的存在,大家只知道运行 Java 程序需要依靠 JVM,千篇一律的配置几个类似 -Xms 和 -Xmx 的参数,可能 ...

  8. 使用mono-repo实现跨项目组件共享

    本文会分享一个我在实际工作中遇到的案例,从最开始的需求分析到项目搭建,以及最后落地的架构的整个过程.最终实现的效果是使用mono-repo实现了跨项目的组件共享.在本文中你可以看到: 从接到需求到深入 ...

  9. LeetCode 面试题16.18.模式匹配

    模式匹配 题目: 你有两个字符串,即pattern和value. pattern字符串由字母"a"和"b"组成,用于描述字符串中的模式.例如,字符串" ...

  10. 【Flutter】功能型组件之跨组件状态共享

    前言   在Flutter开发中,状态管理是一个永恒的话题.   一般的原则是:如果状态是组件私有的,则应该由组件自己管理:如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理.   对于组 ...