跳转至

03 进阶路线:如何深入学习Go语言?

你好,我是郑建勋。

之前的两节课程,我们回顾了一下Go语言的基础知识,掌握这些基础知识已经足够让我们完成一些了不起的项目了。不过,这离深入了解Go语言还有很长的路要走。不管你是想应聘一个更好的工作,希望解决复杂的问题,还是希望更自然地写出高性能的代码,了解语法背后的底层机制都是必不可少的。

然而在实践中,我发现开发者常常容易陷入学习的瓶颈,当一个人现有的知识已经足够应付工作的需要时,他对这门知识的掌握就开始停滞不前,甚至陷入到已经没有提升空间的错误认知中。还有一些人通过面试等打击明白了自己当前知识储备的不足,却苦于找不到破局的方法。

俗话说,授人以鱼不如授人以渔。所以这节课,我们来谈谈如何完成Go语言的进阶学习。这里面的学习方法适用于任何复杂的学科,它可以指导你更好地完成整个课程的学习。

时间与复利思维

互联网时代我们会看到很多光鲜亮丽的表面,然而,真相通常没有其表面看起来那么美好。面对诱惑,需要有不畏浮云遮望眼的智慧,戒骄戒躁,踏实修炼自己的内功。

有名的一万小时定律指出,1万小时的锤炼是普通人变成行业专家的必要条件。实践也表明,在语言学、数学、音乐、物理学、心理学、计算机科学等各种领域,要想成为专家,都需要花费大量时间训练。

说到时间的力量就不得不提到复利思维。复利思维是努力使一件事情随着时间按照指数增长的思维模式。爱因斯坦曾说:“宇宙中最强大的力量就是复利(The most powerful force in the Universe is compound interest.)。”

假设一张纸足够大,我们把它对折,再对折,如此重复对折64次,高度有可能达到166020696万公里。这个长度是什么概念呢?地球到月球的距离才38.4万公里,这就是复利思维的力量。复利思维给我们最大的启发就是,时间的积累最终会产生量变到质变的变化,一个人起点再低,只要在一个方向上持续不断努力,也能逐渐与其他人拉开距离。

图片

当然,要让时间发挥威力,我们必须要找到正确的学习方向才行。就好像我们大多数人学习了多年的应试教育英语还是说不出来,方向不对,再多的努力都是白费。以深入学习Go语言为例,接下来我将介绍几种强大的思维模型。首先让我们来看看:类比。

类比

学习新的知识通常让人有挫败感,通过类比建立知识的模型,将新的知识与以前学到的知识联系起来,将帮助我们更快地理解新的知识。这种类比建立的知识模型是对于知识的简化,它在一开始不需要非常精准,只需要综合一些观念,让这些观念更容易理解和把握就可以了。就像《如何高效学习》一书中提到的:

学习电脑编程时,程序语言经常遇到变量的概念,变量是用来储存信息的,并且在程序运行过程中会发生变化。姓名、数字或是密码都可以作为变量储存起来。我把变量想象成各种各样的罐子,如此一来概念就变得容易理解了。因为变量可以分为好多类型(有的用来储存数字,有的用来储存字母或者单词),我就想象不同的罐子有不同的瓶口,所以可以装不同类型的数据。

图片

我们会发现,对于一个编程语言初学者,缺少内存或者计算机基础的知识,并不妨碍我们理解变量的概念。随着理解的深入,我们可以进一步修正对这一概念的理解了。

再举一个例子,在一开始接触Go语言的协程时,我们可以把协程理解为和线程类似,是操作系统送入CPU中执行的一个代码块。随着学习的深入,我们会进一步更新知识模型,明白协程是如何依托于线程存在的,明白Go运行时调度器是如何调度协程的。

知识组块

当我们已经学到了新领域的诸多知识片段,下一步就是将分散但相关的知识有机地聚合起来,然后赋予其意义,我们称这个过程为组块(chunk)。例如单词m,o,m 单独分开是没有意义的,但是将其组合起来就形成了英文中“妈妈”的意思。

同样地,以Go运行时最复杂的模块垃圾回收为例,它内部的流程非常复杂。但是我们可以将垃圾回收这个复杂的过程分割为几个知识块,分别为标记准备阶段、并行标记阶段、辅助标记阶段、标记清扫阶段等等。

虽然这些知识块背后包含了众多繁琐的细节(例如,如何计算下一次触发GC时的内存,如何将内存还给操作系统)。但是,当我们对知识进行组块时,并不需要记住所有细节,只需要记住关键概念和对应的功能就可以了,这提高了大脑的处理效率。

图片

知识组块让我们避免陷入到知识的细节当中,减少我们学习知识的挫败感。同时,知识组块也是我们构建更大规模知识架构的基础。想一想当今的高端制造业,像是大飞机、芯片制造都凝聚了数代人的智慧结晶,没有一个人、甚至一个国家能够了解这些技术的所有细节,但这并不妨碍我们建造出如此精美的杰作。

再想一想我们在程序设计中,有时不了解编译器的复杂细节也不妨碍我们构建起复杂的系统。恰恰相反,正是因为站在巨人的肩膀上才让我们爬得更高。

知识体系

当我们对知识完成了组块,更重要的是需要理解这些知识块使用的场景,以及不同知识块之间的联系。否则我们就无法真正地发挥这些知识块的作用,甚至不知道应该在何时使用它们。

例如,对于垃圾回收的并行标记这个知识块,我们需要知道这里的并行是什么意思,串行可不可以。这个阶段主要是为了解决什么问题,又为什么还需要额外有辅助标记这个阶段呢?当我们能够回答这些问题的时候,就大致理解了这个阶段做的工作以及之后的阶段需要完成的任务了。

当我们能够建立起知识块之间的联系,就可以形成一个更大范围的知识网络,最终形成完整的知识体系。这就好比我们在完成复杂拼图的时候,通常是拼出有明显特征的块,然后再通过块与块之间的联系完成一幅巨型拼图。

图片

类比到Go语言的进阶学习中也是一样,你需要掌握众多技能,例如切片的底层结构、哈希表的底层结构、协程的调度方式、内存的管理方式等,这都是我们在努力构建的一个个知识块。很快,我们会将这些知识块连成一个更复杂的知识网络。

举一个例子,线程、进程、协程都是一些独立的知识点,但是随着学习的深入,我们开始通过类似GMP模型把这些知识点串联起来,理解了知识间的联系。

在GMP 模型中,G 代表的是Go语言中的协程(Goroutine),M 代表的是实际的线程,而P代表的是Go逻辑处理器(Process)。Go 语言为了方便协程调度与缓存,抽象出了逻辑处理器的概念。在任一时刻,一个P 可能在本地包含多个G,同时,一个P在任一时刻只能绑定一个M。

图片

随着我们对协程、运行时协程调度的理解越来越深入,我们的知识组块、知识体系都可能会有所更新。例如,下面这个改进后的GMP模型就加入了本地运行队列和全局运行队列。它可以让我们更深入地看到调度器的运作模式,每个逻辑处理器 P 中都有单独的本地运行队列用于存储协程,这是为了减少并行时锁的使用。

同时我们也有全局共享的全局运行队列、本地运行队列可以获取全局运行队列中的协程,全局运行队列也可以接收本地运行队列中的协程。

图片

随着知识体系的不断完善,我们逐渐形成了更大的知识网络。我相信当我们搭建起完整的Go知识体系的时候,我们看待程序的视角将会完全不同,就好像可以看到程序内部错综复杂的血管与繁忙流动的血液。

知识体系给我们的启发是,不仅要埋头拉车,更要抬头看路。我们不仅要专注知识的细节,更要看清楚知识之间的联系以及我们现在所处的位置。在阅读一本优秀的书籍或者学习一个优秀的专栏时,我们首先要学习主要概念和要点,这些通常会体现在文章的大纲、流程图、表格或概念图里,然后再阅读详细的信息。这样,即使我们在学习过程中丢失了一些知识细节,仍然不影响我们看到全局。

那我们怎么确保自己已经掌握了重要的知识呢?这就是我接下来想讨论的话题:实践与输出。

实践与输出

很多人会有一种能力上的错觉,觉得自己理解了别人的文章,就是彻底地懂了。但是,会鉴赏一幅画并不意味着你可以自己创作那幅画,能欣赏一首歌也不代表你具备了演唱这首歌的能力,真正搞懂一件事需要你能够真正实践它,这样得到的知识将会更加深刻。

图片

学习金字塔指出,通过阅读或是视听的方式接收到的信息,能够留住的低于30%。而通过实践能够留下的知识却高达75%。大量的案例都表明,软件工程或者说编程是一门实践的科学。纸上得来终觉浅,我们需要反复的实践,才能在报错与调试的折磨中一步步成长。

不过要注意的是,实践并不是简单的重复,而是要不断地用一项超出你当前能力的任务挑战自己,尝试它,分析你在完成它的过程中的表现,然后纠正错误。

学习金字塔同时指出,最好的学习方式其实是教给其他人,这也是费曼学习法的核心要义。当我们把一个知识点从0到1教给了他人,就意味着我们真正掌握了它。很多人会倾向于用复杂的词汇来掩盖他们不明白的东西,但这其实只是在糊弄自己,因为我们不知道自己也不明白。

所以我鼓励你更多地输出自己的知识,这其实也是一个学习的过程。一个闭源的系统,最终将由于熵增而走向死亡,只有不断地接收新的和有用的知识,才能保持活力。

图片

已知与未知

当我们觉得对一个知识点理解得很到位的时候,通常会发现,我们才刚迈进另一个知识的大门。

就拿Go语言的网络模型来说吧,在Linux中Go语言的网络模型封装了操作系统的epoll,那么epoll的结构是怎么样的,它是如何实现对可用socket的监听的?

显然,我们知道的越多,不知道的就越多。不过不同的是,以前我们是不知道自己不知道,而现在我们知道了自己不知道。

这里我想和你分享一个关于芝诺的故事:

一位学生问芝诺:“老师,您的知识比我的知识多许多倍,您对问题的回答又十分正确,可是您为什么总是对自己的解答有疑问呢?”

芝诺顺手在桌上画了一大一小两个圆圈,并指着这两个圆圈说:“大圆圈的面积是我的知识,小圆圈的面积是你们的知识。我的知识比你们多。这两个圆圈的外面就是你们和我无知的部分。大圆圈的周长比小圆圈长,因此,我接触的无知的范围也比你们多。这就是我常常怀疑自己的原因。”

人的知识就好比一个圆圈,圆圈里面是已知的,圆圈外面是未知的。你知道得越多,圆圈也就越大,你不知道的也就越多。

坐井观天,曰天小者,非天小也,其所见小也。夏虫不可与语冰,非无冰也,以其未见冰也。所以说,时刻保持一颗谦虚之心,是求学者应有的态度。

图片

总结

好了,刚才,我们介绍了高效学习的几个重要理论。最后,让我们看看这些方法是怎么指导我们完成Go语言的进阶学习的:

  • 时间的一万小时定律告诉我们,任何知识的深入学习都需要付出大量时间并且掌握正确的方法。因此,我们需要以更沉着的心态面对接下来的学习。同时,复利思维让我们看到了量变到质变,实现弯道超车的可能性,因此我们在学习时需要有足够的信心。
  • 类比思维告诉我们,可以通过恰当的类比助力新知识的理解。这种理解可能在一开始是不准确的,但是它降低了我们学习新事物的挫败感。
  • 知识组块告诉我们可以将学习的内容划分为不同的知识块,每一个知识块将零散且相关的知识有机地聚合起来。在理解整体的知识的基础上,我们可以屏蔽知识块中的实现细节。
  • 知识体系告诉我们,需要理解每一个知识块的使用场景与联系,这样我们才能将知识块关联起来,建立更大规模的知识网络,它可以帮助我们解决更复杂的问题。
  • 实践与输出告诉我们学习不是纸上谈兵,我们需要通过实践和输出的方式来获取较高的学习留存率。在我们后面的项目中,我会把理论和实践结合起来,把众多的知识串联起来。你可以借着这个项目实践这些理论,并为项目做贡献。

我在这里画了一张关于Go语言进阶之路的思维导图,帮助你了解Go语言进阶需要掌握的知识块,你可以用它来查漏补缺,了解自己当前的学习阶段。

深入学习Go的知识能够让我们更好地了解Go语言的语法,做出合理的性能优化,设计科学的程序架构,监控程序的运行状态,排查艰难的程序异常问题,开发高级工具(如检查协程泄露和语法问题的工具),理解Go语言的局限从而在不同场景中做出合理抉择。

学习Go语言底层原理不仅能够提升我们的专业技能和薪资水平,而且它本身就是一种乐趣,这种快乐恰恰是很多自上而下学习编程语言的开发者不能体会的。

在后面的项目中,我会带着你一起在实践中学习核心的Go原理知识。

在这个信息爆炸的世界里,寻找更高效的学习方式变得尤为重要。人脑的工作模式虽然非常复杂,但现代脑神经科学的长足进步给了我们学习者很多深刻的启发。本质上来讲,我们学习的过程就是大脑中神经元相互作用的过程。了解神经网络在底层的运作模式能够让我们对于如何学习有更深的洞见,并指导我们的学习生活。

如果你对这方面感兴趣,我推荐你学习一门在世界范围内都极具影响力的课程:Learning How to Learn,还有一本关于学习原理和学习方法的经典著作《A Mind for Numbers》。

这里我引用Sejnowski博士的一段访谈结束今天的话题,他在神经科学领域做出了卓越的贡献。

Dr.Oakley:关于如何有效地学习,你对年轻的高中生或大学生有什么建议?

Dr.Sejnowski:成功不一定来自聪明。我认识很多不成功的聪明人。但我认识很多人,他们非常非常有激情,并且执着。生活中的许多成功都在于激情和坚持,真正坚持到底,坚持不懈,不放手,不放弃。我认为这真的是我在学生身上看到的最重要的品质。

课后题

最后,我也给你留两道思考题。

  1. 在Go进阶的思维导图中,我列出了进阶需要掌握的诸多知识块,请你认真思考这些知识块对一个程序的作用,找到这些知识块的联系,形成属于你的更大的知识图景。
  2. 一个手电筒有聚焦和发散两种模式,同样的,现代脑神经科学的研究发现,人的大脑也有聚焦模式(focused mode)和发散模式(diffuse mode)两种思维方式,你认为这种现象是如何指导你的学习的?

欢迎你在留言区与我交流讨论,我们下节课再见!

精选留言(15)
  • Dream. 👍(14) 💬(2)

    聚焦模式(focused mode)和发散模式(diffuse mode)两种思维方式。 在每个阶段中这两种学习模式,是交替使用的。 聚焦模式会让我们自己更专注。可往往遇到自己知识盲区时,尤其是无处可学需要自己创新时,又或者无法理解某个知识点时,聚焦模式反而会限制我们的视野。 此时切换成发散模式,不再单独聚焦在需要解决的问题上,往往能给我们带来全新的思路。 发散并不是漫无目的的发散,而是有自顶向下的大局观意识,从而做到形散而神不散。 发散模式带给我们的灵感,往往需要我们自己有着足够的相关知识储备,才能在不同的知识中碰撞出火花。

    2022-10-13

  • ccx 👍(5) 💬(1)

    这个 go 进阶学习导图归纳得很全面!

    2022-10-11

  • 陈东 👍(4) 💬(1)

    老师好,新手,需要提前去看哪些书,才能跟得上课程?谢谢。

    2022-10-16

  • aLong 👍(4) 💬(1)

    看到这章,尤其是那个手电筒比喻。我直接拎出来这本芭芭拉《学习之道》。会议一下,聚焦模式是个起点。没聚焦不能收集学习知识和进行组块。 也就不能再通过发散模式来进行后台处理工作。 切换方式有多种,例如坐车、散步、洗澡等。睡前思考一些问题可以唤起发散模式的处理机制。

    2022-10-13

  • 菜是原罪 👍(4) 💬(5)

    老师 有交流群吗

    2022-10-12

  • WonderChaos 👍(3) 💬(1)

    老师讲的太好了!一定坚持学习!

    2022-10-15

  • Hector 👍(3) 💬(1)

    go的GMP模型是一步步演进来的,P的出现解耦了本地队列和系统线程,降低了锁的使用,P的编排提高了系统线程的使用率,降低了系统的内存,这一块值得细细咀嚼。

    2022-10-14

  • Empty 👍(3) 💬(1)

    虽然已经使用了一年的Go语言,但是看了老师的思维导图之后有太多的东西没有接触过,也有一些虽然会使用但是没有理解过原理,加油

    2022-10-14

  • 温雅小公子 👍(3) 💬(1)

    热血沸腾

    2022-10-13

  • 范飞扬 👍(2) 💬(2)

    思维导图里的defer是不是应该属于语言特性下面?

    2022-10-30

  • 烦烦烦 👍(2) 💬(1)

    这个思维导图太棒了

    2022-10-28

  • 会飞的大象 👍(2) 💬(1)

    关于文章中提到的学习瓶颈期的问题,如何更好监控不知道老师有没有好的方式,现在就遇到了满足了日常工作内容,然后开始停滞的情况,然后感觉有点不对劲(主观上的),然后开始 push 自己主动去学习

    2022-10-23

  • 看海 👍(3) 💬(0)

    收获满满

    2022-10-18

  • 👍(2) 💬(0)

    思维导图 学完消化后就形成体系了

    2022-10-16

  • 文经 👍(1) 💬(0)

    1 . Go进阶路线图里好多知识现在还不懂,学起来。 2 . 费曼学习法用起来,每个模块学完在部门里做个分享。 3 . 芝诺的故事是个好故事,有启发。知道的越多,越知道自己一无所知。

    2022-11-25