当两个或更多的操作必须以正确的顺序执行时,就会出现竞争状态,但如果程序没有写入,无法使操作顺序得到保持。
大多数时候,这出现在所谓的数据竞争中,其中一个并发操作尝试在某些未确定的时间读取变量,而另一个并发操作尝试写入同一个变量。
这里有一个简单的例子:
```
1 var data int
2 go func() { // 1
3 data++
4 }()
5 if data == 0 {
6 fmt.Printf("the value is %v.\n", data)
7 }
```
1. 在Go中,可以使用go关键字同时运行一个函数。 这样做创建了所谓的goroutine。
在第3行和第5行都试图访问名为data的变量,但是并没有施行任何措施保证执行的顺序。运行此代码有三种可能的结果:
* 没有输出。在这种情况下,第3行是在第5行之前执行的。
* 输出 the value is 0。在这种情况下,第5行和第6行在第3行之前执行。
* 输出 the value is 1。在这种情况下,第5行在第3行之前执行,但第3行在第6行之前执行。
正如你所看到的,仅仅几行不确定的代码会在你的程序中引入巨大的变化。
大多数情况下,数据竞争是由于开发人员按顺序思考问题而引入的。 他们认为,上一行代码会先于下一行代码执行。 他们假设在if语句中读取数据变量之前,上面的goroutine将被调度并执行。
在编写并发代码时,你必须仔细地遍历所有可能出现的场景。 除非你正在使用本书稍后部分介绍的一些技巧,否则保证代码将按其在源代码中列出的顺序运行。 我有时会发现在操作之间等待很长一段时间会很有帮助。 想象一下,在调用goroutine的时间和运行的时间之间要经过一个小时。 该程序的其余部分如何运作? 如果在goroutine成功执行和程序到达if语句之间花了一个小时呢? 以这种方式思考对我有所帮助,因为对于计算机而言,规模可能不同,但相对时间差异差不多。
事实上一些开发者确实这么干并发现看起来解决了并发上的问题,我们修改上个例子看看:
```
1 var data int
2 go func() { // 1
3 data++
4 }()
5 time.Sleep(1*time.Second) // 这种做法实在太烂了!
6 if data == 0 {
7 fmt.Printf("the value is %v.\n", data)
8 }
```
我们解决了数据竞争问题吗吗?没有。事实上,从这个方案中产生的所有三个结果仍然是可能的。我们在调用我们的goroutine和检查数据值之间的让程序休眠的时间越长,程序越接近实现正确性——但这只是在概率上渐近地接近逻辑正确而已。
除此之外,这样做已经在算法中引入了低效率。 程序现在必须休眠一秒钟才能使我们更有可能看不到的数据竞争。如果我们使用正确的方式来编写代码,我们可能无需等待,或者等待时间可能只有1微秒。
这里要说的是,你应该总是以逻辑的正确性为目标。 在你的代码中引入休眠可以是一种调试并发程序的方便方式,但不是解决方案。
数据竞争的产生条件是最隐秘的并发错误类型之一,因为它们可能在代码投入生产后才会展现出来。 它们通常是由代码执行环境发生变化或前所未有的突发事件引起的。 在这些情况下,代码看起来行为正确,但实际上,这些操作按顺序执行的出现不确定性的几率非常高。
* * * * *
学识浅薄,错误在所难免。我是长风,欢迎来Golang中国的群(211938256)就本书提出修改意见。
- 前序
- 谁适合读这本书
- 章节导读
- 在线资源
- 第一章 并发编程介绍
- 摩尔定律,可伸缩网络和我们所处的困境
- 为什么并发编程如此困难
- 数据竞争
- 原子性
- 内存访问同步
- 死锁,活锁和锁的饥饿问题
- 死锁
- 活锁
- 饥饿
- 并发安全性
- 优雅的面对复杂性
- 第二章 代码建模:序列化交互处理
- 并发与并行
- 什么是CSP
- CSP在Go中的衍生物
- Go的并发哲学
- 第三章 Go的并发构建模块
- Goroutines
- sync包
- WaitGroup
- Mutex和RWMutex
- Cond
- Once
- Pool
- Channels
- select语句
- GOMAXPROCS
- 结论
- 第四章 Go的并发编程范式
- 访问范围约束
- fo-select循环
- 防止Goroutine泄漏
- or-channel
- 错误处理
- 管道
- 构建管道的最佳实践
- 便利的生成器
- 扇入扇出
- or-done-channel
- tee-channel
- bridge-channel
- 队列
- context包
- 小结
- 第五章 可伸缩并发设计
- 错误传递
- 超时和取消
- 心跳
- 请求并发复制处理
- 速率限制
- Goroutines异常行为修复
- 本章小结
- 第六章 Goroutines和Go运行时
- 任务调度