golang读出文件中的一个数字_用一个小例子谈谈Golang 中的Race Condition

Goroutine 是Go 最重要的特性之一,它可以让开发者轻易做到并发(concurrency),而且他的非常轻量,所以一次开一大堆goroutine 也不会有什么问题。

但如果在使用goroutine 时没有考虑到race condition,那可能就会导致不正确的结果,这篇文章会用一个简单的小例子,谈谈在什么情况可能会遇到race condition,以及如何发现、解决它。

8d2f3ca96d4a3d80471cf9afa7d6b179.png

Race Condition(竞争危害)

譬如说有两个正在进行中的goroutine分别要对某变数a做a = a * 2还有a = a + 1,这两个goroutine不同的顺序有可能导致最后a有不同的值,这就是race condition,为了防止race condition要使用一些特别的方式让他们有确定的顺序,以免导致奇怪的bug

来看看这次要讲解的例子,分成三个步骤

  1. 先把a 的初始值设为 0
  2. 开三个goroutine 共做了三次 a++
  3. 最后用channel 等待三个goroutine 完成

没意外的话最后应该会得到a = 3,结果也确实如此

那如果把次数改成一万次呢?

理论上要得到a = 10000,实际跑了却会得到a = 9903之类的结果(如果你有多核CPU的话),但我们确实开了一万个goroutine也做了一万次a++,为什么结果会不对呢,因为在a++的过程中发生了race condition

为什么a++ 会发生race condition

当你写了a++时电脑实际上做了三件事:

  1. CPU 把a 的值取出来
  2. 把刚刚取得的值加 1
  3. 把运算的结果存回变数 a

但万一你有多核CPU就有可能会这样:

3d516ef1ae71746c8cd67062c03da6c8.png

两个CPU 同时去拿变数a 的值,各自加1 后存回,导致a 只被加了一次,因此结果(9903)会小于正确的10000

解法:互斥锁

这里会发生race condition最根本的原因是「两个goroutine可能会同时存取变数a」,如果能限制同时只能有一个 goroutine做a++,那就能解决这个问题,为了达到这个目的我们要使用sync.Mutex

Mutex是互斥锁的意思,同时最多只能有一个人拿到这个锁,其他人想要锁的话就会被block住直到前一个人用完,所以就可以确保只有一个goroutine正在进行a++,这样就可以得到正确的结果10000

如何发现race condition

在这个例子中race condition发生在a++,但如果对电脑底层不够熟悉就有可能没办法发现问题,还好Golang有个很强大的工具叫Data Race Detector

在跑的时候加一个-race他就可以帮你侦测哪边可能会产生race condiction,大家也可以自己下载原码下来跑

$ go run -race add_few_times/main.go ================== WARNING: DATA RACE Read at 0x00c4200a4008 by goroutine 7:  main.main.func1()  .../ add_few_times/main.go:12 +0x38Previous write at 0x00c4200a4008 by goroutine 6:  main.main.func1()  .../add_few_times/main.go:12 +0x4eGoroutine 7 (running) created at:  main.main()  .../add_few_times/main.go:11 +0xc1Goroutine 6 (running) created at:  main.main()  .../add_few_times/main.go:11 +0xc1 ================== Found 1 data race(s ) exit status 66

Race Detector说在第11行(go func(){…}())产生了两个goroutine(G6跟G7),G7在第十二行(a++)读取了变数a之后,G6紧接着写入了变数a,所以G7会读到旧资料,这时候就有可能会产生race condition

透过Race Detector 几乎可以找到所有的race condition,大部分时候也都只要加个锁就可以解决

锁的缺点

性能

上面的例子用mutex 来防止多个goroutine 同时存取同一个变数,因为总共有一万个goroutine,当你有其中一个正在存取a 时其他9999 个都在等他,他们之间完全没有并行( parallelism),不如用个回圈把它从0 加到10000 可能还更快

所以在使用锁时一定要非常小心,只在必要的时候使用,否则效能将会大打折扣

忘记解锁

有时候上锁解锁不像上面a++这么简单,中间可能有很多个锁还有各种条件判断、网路请求等等,当程式变复杂一不小就有可能忘记或是太晚解锁,造成整个程式非常慢甚至完全卡住,产生死锁问题

总结

这次用很简单的例子谈谈在Golang 中什么时候会遇到race condition 以及如何解决,因为要开一个goroutine 太简单了,所以有时候会不小心忘记考虑race condition,还好Go 有提供Race Detector 不用自己慢慢找XDD

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值