82 lines
10 KiB
Markdown
82 lines
10 KiB
Markdown
|
# 开篇词 | 想吃透Go并发编程,你得这样学!
|
|||
|
|
|||
|
你好,我是晁岳攀,网名鸟窝。之前我在微博研发平台架构中心担任资深架构师,同时也是微服务框架rpcx的作者,欢迎来到“Go并发编程实战课”。
|
|||
|
|
|||
|
## 并发编程,为什么选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测试](https://colobu.com/2020/01/21/benchmark-2019-spring-of-popular-rpc-frameworks/),rpcx有着数一数二的性能,很多互联网企业(比如马蜂窝、百度等)都在使用。
|
|||
|
|
|||
|
在微博的四年时间里,我使用Go参与开发多个基础架构系统,并负责中国版权链、微博下一代的Redis集群系统、数据库资源云等系统的设计和开发工作。在多年的实战中,我遇见过各种各样的并发难题,积累了大量的高并发高吞吐的服务器开发经验,也梳理了一整套并发编程的知识体系。
|
|||
|
|
|||
|
2019年,astaxie(谢孟军)邀请我在Gopher China大会上做一个关于Go并发编程的分享。我准备了一份120页的[PPT](https://github.com/smallnest/dive-to-gosync-workshop/files/6732617/Go.pptx),全面地介绍了Go并发编程的基础内容,包括基本并发原语、扩展并发原语和Channel等。会后,现场的观众都说干货满满,希望我能提供无删改版的PPT。
|
|||
|
|
|||
|
后来,在Go爱好者的强烈要求下,我又在滴滴举办了一场Go并发编程的培训,详细地分享了我的并发编程心得和经验,包括各种并发原语的基本用法和实现机制。
|
|||
|
|
|||
|
结合我自己的开发经验,以及这些年的技术分享经历,我真切地感受到了这一点:**Go并发编程的重要性不容置疑。只要是使用Go开发的大型应用程序,并发是必然要采用的技术。**
|
|||
|
|
|||
|
但同时,我也了解到,很多人想要学习Go并发编程,却不知道该从何学起,也不知该如何精进。
|
|||
|
|
|||
|
## 学习Go并发编程,有哪些困难?
|
|||
|
|
|||
|
那学习Go并发会有哪些困难呢?我总结了一下,主要是有5大问题。
|
|||
|
|
|||
|
1. 在面对并发难题时,感觉无从下手,不知道**该用什么并发原语来解决问题**。
|
|||
|
2. 如果多个并发原语都可以解决问题,那么,**究竟哪个是最优解呢**?比如说是用互斥锁,还是用Channel。
|
|||
|
3. **不知道如何编排并发任务**。并发编程不像是传统的串行编程,程序的运行存在着很大的不确定性。这个时候,就会面临一个问题,**怎么才能让相应的任务按照你设想的流程运行呢**?
|
|||
|
4. 有时候,按照正常理解的并发方式去实现的程序,结果莫名其妙就panic或者死锁了,**排查起来非常困难**。
|
|||
|
5. **已知的并发原语都不能解决并发问题**,程序写起来异常复杂,而且代码混乱,容易出错。
|
|||
|
|
|||
|
每一位刚入门Go的程序员,在深入学习Go语言的时候,尤其是面对Go并发编程的时候,都会遇到这些问题。那么,具体该怎么学呢?
|
|||
|
|
|||
|
## 怎么提升Go并发编程能力?
|
|||
|
|
|||
|
学习这件事儿,最怕的就是不成体系,即使知识点之间是彼此独立的,也必定存在着联系。我们要做的,就是找出逻辑关系,拎出知识线。我认为,**关于Go并发编程,有两条主线,分别是知识主线和学习主线**。具体是啥意思呢?可以看下面的这张知识地图。
|
|||
|
|
|||
|
![](https://static001.geekbang.org/resource/image/81/e3/81fa1cfd8d39632d871baeedf4081ce3.jpg?wh=4000*2250)
|
|||
|
|
|||
|
从图中可以看到,在知识主线层面,这门课程的核心内容设计了5个模块:
|
|||
|
|
|||
|
* **基本并发原语**:在这部分,我会介绍Mutex、RWMutex、Waitgroup、Cond、Pool、Context等标准库中的并发原语,这些都是传统的并发原语,在其它语言中也很常见,是我们在并发编程中常用的类型。
|
|||
|
* **原子操作:**在这部分,我会介绍Go标准库中提供的原子操作。原子操作是其它并发原语的基础,学会了你就可以自己创造新的并发原语。
|
|||
|
* **Channel**:Channel类型是Go语言独特的类型,因为比较新,所以难以掌握。但是别怕,我会带你全方位地学习Channel类型,你不仅能掌握它的基本用法,而且还能掌握它的处理场景和应用模式,避免踩坑。
|
|||
|
* **扩展并发原语**:目前来看,Go开发组不准备在标准库中扩充并发原语了,但是还有一些并发原语应用广泛,比如信号量、SingleFlight、循环栅栏、ErrGroup等。掌握了它们,就可以在处理一些并发问题时,取得事半功倍的效果。
|
|||
|
* **分布式并发原语**:分布式并发原语是应对大规模的应用程序中并发问题的并发类型。我主要会介绍使用etcd实现的一些分布式并发原语,比如Leader选举、分布式互斥锁、分布式读写锁、分布式队列等,在处理分布式场景的并发问题时,特别有用。
|
|||
|
|
|||
|
![](https://static001.geekbang.org/resource/image/7a/de/7af6e5216dd0cc449878b3949a3e81de.jpg?wh=750*2137)
|
|||
|
|
|||
|
沿着这条知识主线,我会带你建立起一个丰富的并发原语库。你可以把并发问题当成一个强大的敌人,而这些并发原语,就是我们的武器。每一种并发原语都有它的用处,你只有知道足够多的并发原语,才能灵活地应对各种场景。
|
|||
|
|
|||
|
那具体怎么掌握这些武器呢?课程的每一个模块都是独立的,它们之间没有任何依赖问题,你可以结合自己的实际情况,有重点地进行学习。如果你对Channel类型不是太熟悉,就可以先看Channel这个模块的内容;如果你已经非常熟悉标准库的并发原语了,就可以看看扩展并发原语和分布式并发原语的内容。
|
|||
|
|
|||
|
同时,在学习主线层面,主要是**四大步骤,包括基础用法、实现原理、易错场景、知名项目中的Bug**。每一个模块,我都会带着你按照这四个步骤来学习,目的就是带你熟知每一种并发原语的实现机制和适用场景。
|
|||
|
|
|||
|
Go中有一个大的方向,就是任务编排用Channel,共享资源保护用传统并发原语。在刚开始学习时,你可以基于这个原则去选择相应的并发原语,这是没错的。但是,如果你想要在Go并发编程的道路上向前走,就不能局限于这个原则。
|
|||
|
|
|||
|
实际上,针对同一种场景,也许存在很多并发原语都适用的情况,但是一定是有最合适的那一个。所以,你必须非常清楚每种并发原语的实现机制和适用场景,千万不要被网上的一些文章误导,万事皆用Channel。
|
|||
|
|
|||
|
而且,你还可以深入学习下Go并发原语的源代码。你会发现很多独到的设计,比如Mutex为了公平性考量的设计、sync.Map为提升性能做的设计,以及很多并发原语的异常状况的处理方式。尤其是这些异常状况,常常是并发编程中程序panic的原因。
|
|||
|
|
|||
|
所以,如果你能深入了解这些并发原语的实现,不但会提高你的编程能力,还能让你避免在开发中踩并发问题的坑。这个时候,你就达到精通的程度了。
|
|||
|
|
|||
|
如果没有做过大型并发项目,你可能还不太清楚并发原语的重要性。那么,我建议你先阅读一下课程中介绍的知名项目中犯的错,这也是这门课里我特别设计的一部分内容。通过理解这些Go大牛们犯的错误以及解决方案,你就可以积累一套避坑指南和应对之道。
|
|||
|
|
|||
|
有了这两条线的学习,我们就从广度和深度上掌握了Go并发编程的知识点。这些是不是就足够了呢?我们还可以更进一步,你要有野心能够创造出自己需要的并发原语。
|
|||
|
|
|||
|
这里的创造有两层含义。第一层是对既有的并发原语进行组合,使用两个、三个或者更多的并发原语去解决问题。比如说,我们可以通过信号量和WaitGroup组合成一个新的并发原语,这个并发原语可以使用有限个goroutine并发处理子任务。第二层含义是“无中生有”,根据已经掌握的并发原语的设计经验,创造出合适的新的并发原语,以应对一些特殊的并发问题。比如说,标准库中并没有信号量,你可以自己创造出这个类型。
|
|||
|
|
|||
|
达到了这一层,那就不得了了,可以说你对Go并发原语的掌握已经出神入化了。那想要达到这个程度是不是很难呢?确实不容易,不过我相信,如果你仔细学习了我们课程里的每一节课,心里牢牢地锚定3个目标:建立起一个丰富的并发原语库;熟知每一种并发原语的实现机制和适用场景;能够创造出自己需要的并发原语。达到了这3个目标,你就可以轻松地应对各种并发问题了。甚至可以说,你几乎能站在Go并发编程的顶端,成为大牛中的一员。
|
|||
|
|
|||
|
最后,我想说的是,Go并发编程的世界确实纷繁复杂,涉及到的内容非常多。你可以把它看作是一个江湖,如果你想拥有极强的作战力,就要拥有足够多的武器,并且修炼内功。这门课,就是你的修炼山洞,我准备了应有尽有的宝藏,等待着你来挖掘。
|
|||
|
|
|||
|
修炼的过程中,最好有人和你并肩而行,共同成长。欢迎你把这门课分享给你的朋友或同事,和他/她一起提升并发编程的功力。
|
|||
|
|