你好,我是晁岳攀,网名鸟窝。之前我在微博研发平台架构中心担任资深架构师,同时也是微服务框架rpcx的作者,欢迎来到“Go并发编程实战课”。
为什么要学Go并发呢?我想先和你聊聊我和Go结缘的经历。
作为一位老程序员,我在清华同方、摩托罗拉、Comcast等公司,一直使用Java做项目开发。但是后来,我毅然抛弃了十几年的Java编程经验,投入到了Go语言的怀抱,为什么呢?
一句话,我被Go的简单高效所打动。它不仅部署方便,自带完善的工具链,特别是Go在处理并发场景上表现出的独特性能,更是让我着迷。
我们知道,Java语言的编码非常繁琐,为了应用设计模式而做了大量的冗长设计,而Go就不一样了。它提供了便利的并发编程方式,简简单单的Go语句,就可以创建多个goroutine执行并发任务。而且,Go还提供了独特的Channel类型,很容易实现goroutine之间的数据交流。所以,Go并发编程入门很容易,即使是初学者,要写一个使用goroutine异步输出“Hello World”的例子,也可以不费吹灰之力。
不过,和其他语言相比,Go微服务治理框架的发展还是比较晚的。当阿里出品的Java微服务框架Dubbo被广泛应用时,Go生态圈还没有微服务框架。
于是,四五年前,为了填补Go生态圈微服务化的缺失,我就用Go开发了一个微服务的框架rpcx。它既有类似标准rpc库的易用特点,又包含了非常丰富的服务治理的功能。而且,根据benchmark测试,rpcx有着数一数二的性能,很多互联网企业(比如马蜂窝、百度等)都在使用。
在微博的四年时间里,我使用Go参与开发多个基础架构系统,并负责中国版权链、微博下一代的Redis集群系统、数据库资源云等系统的设计和开发工作。在多年的实战中,我遇见过各种各样的并发难题,积累了大量的高并发高吞吐的服务器开发经验,也梳理了一整套并发编程的知识体系。
2019年,astaxie(谢孟军)邀请我在Gopher China大会上做一个关于Go并发编程的分享。我准备了一份120页的PPT,全面地介绍了Go并发编程的基础内容,包括基本并发原语、扩展并发原语和Channel等。会后,现场的观众都说干货满满,希望我能提供无删改版的PPT。
后来,在Go爱好者的强烈要求下,我又在滴滴举办了一场Go并发编程的培训,详细地分享了我的并发编程心得和经验,包括各种并发原语的基本用法和实现机制。
结合我自己的开发经验,以及这些年的技术分享经历,我真切地感受到了这一点:Go并发编程的重要性不容置疑。只要是使用Go开发的大型应用程序,并发是必然要采用的技术。
但同时,我也了解到,很多人想要学习Go并发编程,却不知道该从何学起,也不知该如何精进。
那学习Go并发会有哪些困难呢?我总结了一下,主要是有5大问题。
每一位刚入门Go的程序员,在深入学习Go语言的时候,尤其是面对Go并发编程的时候,都会遇到这些问题。那么,具体该怎么学呢?
学习这件事儿,最怕的就是不成体系,即使知识点之间是彼此独立的,也必定存在着联系。我们要做的,就是找出逻辑关系,拎出知识线。我认为,关于Go并发编程,有两条主线,分别是知识主线和学习主线。具体是啥意思呢?可以看下面的这张知识地图。
从图中可以看到,在知识主线层面,这门课程的核心内容设计了5个模块:
沿着这条知识主线,我会带你建立起一个丰富的并发原语库。你可以把并发问题当成一个强大的敌人,而这些并发原语,就是我们的武器。每一种并发原语都有它的用处,你只有知道足够多的并发原语,才能灵活地应对各种场景。
那具体怎么掌握这些武器呢?课程的每一个模块都是独立的,它们之间没有任何依赖问题,你可以结合自己的实际情况,有重点地进行学习。如果你对Channel类型不是太熟悉,就可以先看Channel这个模块的内容;如果你已经非常熟悉标准库的并发原语了,就可以看看扩展并发原语和分布式并发原语的内容。
同时,在学习主线层面,主要是四大步骤,包括基础用法、实现原理、易错场景、知名项目中的Bug。每一个模块,我都会带着你按照这四个步骤来学习,目的就是带你熟知每一种并发原语的实现机制和适用场景。
Go中有一个大的方向,就是任务编排用Channel,共享资源保护用传统并发原语。在刚开始学习时,你可以基于这个原则去选择相应的并发原语,这是没错的。但是,如果你想要在Go并发编程的道路上向前走,就不能局限于这个原则。
实际上,针对同一种场景,也许存在很多并发原语都适用的情况,但是一定是有最合适的那一个。所以,你必须非常清楚每种并发原语的实现机制和适用场景,千万不要被网上的一些文章误导,万事皆用Channel。
而且,你还可以深入学习下Go并发原语的源代码。你会发现很多独到的设计,比如Mutex为了公平性考量的设计、sync.Map为提升性能做的设计,以及很多并发原语的异常状况的处理方式。尤其是这些异常状况,常常是并发编程中程序panic的原因。
所以,如果你能深入了解这些并发原语的实现,不但会提高你的编程能力,还能让你避免在开发中踩并发问题的坑。这个时候,你就达到精通的程度了。
如果没有做过大型并发项目,你可能还不太清楚并发原语的重要性。那么,我建议你先阅读一下课程中介绍的知名项目中犯的错,这也是这门课里我特别设计的一部分内容。通过理解这些Go大牛们犯的错误以及解决方案,你就可以积累一套避坑指南和应对之道。
有了这两条线的学习,我们就从广度和深度上掌握了Go并发编程的知识点。这些是不是就足够了呢?我们还可以更进一步,你要有野心能够创造出自己需要的并发原语。
这里的创造有两层含义。第一层是对既有的并发原语进行组合,使用两个、三个或者更多的并发原语去解决问题。比如说,我们可以通过信号量和WaitGroup组合成一个新的并发原语,这个并发原语可以使用有限个goroutine并发处理子任务。第二层含义是“无中生有”,根据已经掌握的并发原语的设计经验,创造出合适的新的并发原语,以应对一些特殊的并发问题。比如说,标准库中并没有信号量,你可以自己创造出这个类型。
达到了这一层,那就不得了了,可以说你对Go并发原语的掌握已经出神入化了。那想要达到这个程度是不是很难呢?确实不容易,不过我相信,如果你仔细学习了我们课程里的每一节课,心里牢牢地锚定3个目标:建立起一个丰富的并发原语库;熟知每一种并发原语的实现机制和适用场景;能够创造出自己需要的并发原语。达到了这3个目标,你就可以轻松地应对各种并发问题了。甚至可以说,你几乎能站在Go并发编程的顶端,成为大牛中的一员。
最后,我想说的是,Go并发编程的世界确实纷繁复杂,涉及到的内容非常多。你可以把它看作是一个江湖,如果你想拥有极强的作战力,就要拥有足够多的武器,并且修炼内功。这门课,就是你的修炼山洞,我准备了应有尽有的宝藏,等待着你来挖掘。
修炼的过程中,最好有人和你并肩而行,共同成长。欢迎你把这门课分享给你的朋友或同事,和他/她一起提升并发编程的功力。
评论