最后,我们谈到了开发并发代码的最困难的方面,它是所有其他问题的基础:人。 每行代码的编写者至少有一个人。
正如我们发现的,并发代码的难题由很多原因产生。 如果你是一名开发人员,并且在引入新功能时尝试解决所有这些问题,或修复程序中的错误,确定什么样的操作是正确的确实很困难。
如果你从零开始构建程序,需要建立一个合理的方式来模拟问题,但如果涉及到并发,就可能很难找到合适的抽象级别。你该如何向调用者暴露并发接口?应该使用什么样的技术使之简单而有效?应该支持什么样的并发规模?有不同的结构化方式来思考这些问题,但这些问题的解决方案有时候更接近艺术而不是技术。
作为一个对已有代码改造的开发人员,哪些代码利用并发并不总是很明显,如何安全地使用前人的代码有时更与智力无关。考虑下面的函数声明:
```
// CalculatePi 会在开始和结束位置之间计算Pi的数字
func CalculatePi(begin, end int64, pi *Pi)
```
以较高精度计算pi是最好的方法,但这个例子引发了很多问题:
* 我该如何调用这个函数?
* 我是否负责实例化此函数的多个并发调用?
* 看起来函数的所有实例都将直接在我传入地址的Pi实例上运行; 是由我负责同步对内存的访问,还是函数为我处理?
仅此一个函数就引发了这些问题。 想象一下任何规模适中的程序,你就可以开始理解并发可能带来的复杂性。
注释可以在这里创造奇迹。如果函数是这样写的呢?
```
// CalculatePi 会在开始和结束位置之间计算Pi的数字
//
// 在内部,CalculatePi会创建FLOOR((end-begin)/ 2)递归调用
// CalculatePi的并发进程。 写入pi的同步锁由Pi结构内部处理。
func CalculatePi(begin, end int64, pi *Pi)
```
我们现在明白,调用者可以简单地调用该函数,而不必担心访问控制或同步问题。 重要的是,注释涵盖了这些方面:
* 谁负责并发?
* 问题空间如何映射到并发基元?
* 谁负责同步?
当需要暴露涉及并发问题的函数、方法和变量时,请尽可能让你的同事和未来的自己受益:不一定非要写出冗长的注释,但请尽量覆盖上面的三个要素。
还要考虑到函数命名在含义上的模糊。也许我们应该让函数看起来没有副作用:
```
func CalculatePi(begin, end int64) []uint
```
这个函数的签名本身就消除了任何同步问题的疑问,但仍然留下了是否使用并发的问题。 我们可以再次修改签名,以明确的告诉调用者我们要返回什么:
```
func CalculatePi(begin, end int64) <-chan uint
```
现在我们首次看到了被称为channel(通道)的用法。随后在第三章会有更详细的介绍。修改后的函数签名表明CalculatePi将至少有一个goroutine,我们不应该为创建自己的goroutine而烦恼。
然后,这些修改会产生性能影响,必须予以考虑,我们又回到了平衡清晰度与性能之间的问题。 清晰性非常重要,因为我们希望将来尽可能使用此代码的人能够做正确的事情,并且由于显而易见的原因,性能很重要。 两者不是相互排斥的,但它们很难同时被处理的很好。
体会下我们在上面遇到的各种困难,并尝试将它们扩展到团队规模。
喔,真是个相当可怕的情景。
好消息是,Go已经逐步的给出了简单实用的解决方案。语言本身就具备了较强的可读性而又不失简约。Go鼓励并发建模的正确性,可组合性和可伸缩性。事实上,Go处理并发的方式实际上可以帮助你更清楚地表达问题。 让我们来看看为什么这么说。
* * * * *
学识浅薄,错误在所难免。我是长风,欢迎来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运行时
- 任务调度