相比于细节,更在意知识框架的构建和完善,因此有时候对一些技术细节不是很清楚,只是知道如何找答案。最近要认真编码,需要仔细考虑、敲定细节,趁此机会将 Go 语言的知识整理一下。
找到正确的资料、能够正确的使用、正确的理解,是最关键的一步。除非是初学者,否则不要使用二手、三手和倒了无数手的资料,长期来看使用非一手资料,就是在浪费时间和引入错误。 第一手的资料常常晦涩难懂,需要经过长时间的积累和沉淀,才能比较熟练的运用,刚开始的时候可以用二手、三手的资料帮助理解,但一定要逼迫自己在一手资料中找到解答,这个过程会极大地提升认知。
下面以Go 语言的 map 是否是并发安全的?
为例,演示一下 Go 语言的一手资料的用法。
最权威的 Go 语言资料是什么?Go 官网上的语言规范:The Go Programming Language Specification。
这份资料可以理解为 Go 语言的设计文档之一,用表达式的方式对 Go 语言语法做出了最精确的定义,对 Go 语言特性的介绍也是最全面的。掌握了这一份资料,基本就掌握了 100% 的 Go 语言语法和大部分使用时应注意的细节。如果要了解 Go 语言的实现和一些更复杂的内容,需要去查阅其它文档。
Go Spec 不容易看懂,里面的每一个英文单词都认识,但是联在一起就看不明白了。因为这份文档是在从更高的维度描述 Go 语言,使用的是「编程入门」类书籍中没有的术语,譬如Lexical elements
、Assignability
,就好像把「桌子」称为「一种用木材制作而成的家具」一样。习惯了这种表达方式,并知道常见「称呼」的含义后,会发现从这份文档中找答案的效率非常高!
先到 Go Spec 的 map 章节中阅读 map 的细节:
map 是这样定义的:
A map is an unordered group of elements of one type, called the element type,
indexed by a set of unique keys of another type, called the key type.
MapType = "map" "[" KeyType "]" ElementType .
KeyType = Type .
从这一章里得知:
make
函数创建,未初始化的 map
不等于 空的map
;size 是无限制的
,用 make 函数创建 map 时,设置的 capacity 不会限制 map 的 size,map 的容量是随着 element 的插入动态增长的;遗憾的是,在这里没有找到对并发操作的说明,用 golang.org 的站内搜索也没找到相关的信息。
这时候用 Google 搜到了 stackoverflow 上的一篇问答:How safe are Golang maps for concurrent Read/Write operations?,引起注意的是这篇问答中的两个链接:
第一个连接是 Go 官方博客的一篇文章,第二个连接是 Go 1.6 的 Release Notes,然后又从第一个连接中找到了一个 Go 问答:
把这个网页下拉到顶部,发现了一个宝藏,Frequently Asked Questions (FAQ):
从上面几个链接中的内容得知,Go 的 map 不是并发安全的,这是设计人员经过长期讨论做出的决定:因为大部分使用 map 的场景不需要并发读写,如果将 map 设计为并发安全的,将降低大多数程序的性能。
只有在更新 map 的时候,map 才是并发不安全的,全部是读操作的并发是安全的。Go 1.6 对 map 的并发读写进行了更明确的规定:当一个协程正在对 map 进行写操作的时候,不能有其它协程在对同一个 map 进行操作,读和写都不行。Go 的 runtime 会对 map 的并发读写进行监测,如果发现不安全的操作直接 crash。
上一节找到了Go 语言的 map 是否是并发安全的?
这个问题的答案,但是事情没有结束。
在上一节找答案的过程中,纯粹是运气好,遇到三篇预期外的文档,一篇是 Go 的博客文章,一篇是 Go 的 Release Notes, 一篇是 Go 的 FAQ,这三篇权威的官方文档给出了准确的答案。
权威资料得到扩充的同时第一个问题来了:这次是运气好,用 Google 找到了这三篇官方的权威资料,如果下次查找其它问题的答案时,运气没这么好该怎么办?
在 Go 的官网上溜达,最后发现这三篇文档都可以通过 Documentation 页面中的连接找到:
前面的操作也让我们知道了, golang.org 站内搜索的结果是不全面的,用 Google 进行站内搜索能找到更多内容:
第二个问题是:既然基本类型 map 是并发读写不安全的,那么要怎样实现并发读写安全的 map 呢?自己加锁实现不难,不过继续查一下会发现,Go 标准库中提供了一个通用的、并发读写安全的 type Map:
如果技术热情高涨,可以继续研究下标准库中的实现有什么特点,毕竟提出需求的 issues/18177 中说了一句:“RWMutex has some scaling issues,巴拉巴拉巴……”。伪技术迷就先告辞做任务去了 😭……
因为一手资料是最全的、并且会及时更新的资料。一本《Go 语言编程入门》出版之后,几年内都不会变化,而几年的时间对于一门编程语言来说很久了,如果只会从书本里找答案,那么永远不会知道最新的情况。
一手资料以外的资料,很难做到及时更新,所以我们才会经常用 Google 、baidu 搜索到过时的解答和方案。有些非一手资料还是不靠谱的,漫天搜索、左右尝试的不仅耗费大量时间,而且即使碰巧找到正确方法把问题解决了,依旧云里雾里不知其所以然,下次遇到同一个领域的问题,还要漫天搜索。