You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

10 KiB

开篇词 | 想吃透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测试rpcx有着数一数二的性能很多互联网企业比如马蜂窝、百度等都在使用。

在微博的四年时间里我使用Go参与开发多个基础架构系统并负责中国版权链、微博下一代的Redis集群系统、数据库资源云等系统的设计和开发工作。在多年的实战中我遇见过各种各样的并发难题积累了大量的高并发高吞吐的服务器开发经验也梳理了一整套并发编程的知识体系。

2019年astaxie谢孟军邀请我在Gopher China大会上做一个关于Go并发编程的分享。我准备了一份120页的PPT全面地介绍了Go并发编程的基础内容包括基本并发原语、扩展并发原语和Channel等。会后现场的观众都说干货满满希望我能提供无删改版的PPT。

后来在Go爱好者的强烈要求下我又在滴滴举办了一场Go并发编程的培训详细地分享了我的并发编程心得和经验包括各种并发原语的基本用法和实现机制。

结合我自己的开发经验,以及这些年的技术分享经历,我真切地感受到了这一点:Go并发编程的重要性不容置疑。只要是使用Go开发的大型应用程序并发是必然要采用的技术。

但同时我也了解到很多人想要学习Go并发编程却不知道该从何学起也不知该如何精进。

学习Go并发编程有哪些困难

那学习Go并发会有哪些困难呢我总结了一下主要是有5大问题。

  1. 在面对并发难题时,感觉无从下手,不知道该用什么并发原语来解决问题
  2. 如果多个并发原语都可以解决问题,那么,究竟哪个是最优解呢比如说是用互斥锁还是用Channel。
  3. 不知道如何编排并发任务。并发编程不像是传统的串行编程,程序的运行存在着很大的不确定性。这个时候,就会面临一个问题,怎么才能让相应的任务按照你设想的流程运行呢
  4. 有时候按照正常理解的并发方式去实现的程序结果莫名其妙就panic或者死锁了排查起来非常困难
  5. 已知的并发原语都不能解决并发问题,程序写起来异常复杂,而且代码混乱,容易出错。

每一位刚入门Go的程序员在深入学习Go语言的时候尤其是面对Go并发编程的时候都会遇到这些问题。那么具体该怎么学呢

怎么提升Go并发编程能力

学习这件事儿,最怕的就是不成体系,即使知识点之间是彼此独立的,也必定存在着联系。我们要做的,就是找出逻辑关系,拎出知识线。我认为,关于Go并发编程有两条主线分别是知识主线和学习主线。具体是啥意思呢?可以看下面的这张知识地图。

从图中可以看到在知识主线层面这门课程的核心内容设计了5个模块

  • 基本并发原语在这部分我会介绍Mutex、RWMutex、Waitgroup、Cond、Pool、Context等标准库中的并发原语这些都是传统的并发原语在其它语言中也很常见是我们在并发编程中常用的类型。
  • **原子操作:**在这部分我会介绍Go标准库中提供的原子操作。原子操作是其它并发原语的基础学会了你就可以自己创造新的并发原语。
  • ChannelChannel类型是Go语言独特的类型因为比较新所以难以掌握。但是别怕我会带你全方位地学习Channel类型你不仅能掌握它的基本用法而且还能掌握它的处理场景和应用模式避免踩坑。
  • 扩展并发原语目前来看Go开发组不准备在标准库中扩充并发原语了但是还有一些并发原语应用广泛比如信号量、SingleFlight、循环栅栏、ErrGroup等。掌握了它们就可以在处理一些并发问题时取得事半功倍的效果。
  • 分布式并发原语分布式并发原语是应对大规模的应用程序中并发问题的并发类型。我主要会介绍使用etcd实现的一些分布式并发原语比如Leader选举、分布式互斥锁、分布式读写锁、分布式队列等在处理分布式场景的并发问题时特别有用。

沿着这条知识主线,我会带你建立起一个丰富的并发原语库。你可以把并发问题当成一个强大的敌人,而这些并发原语,就是我们的武器。每一种并发原语都有它的用处,你只有知道足够多的并发原语,才能灵活地应对各种场景。

那具体怎么掌握这些武器呢课程的每一个模块都是独立的它们之间没有任何依赖问题你可以结合自己的实际情况有重点地进行学习。如果你对Channel类型不是太熟悉就可以先看Channel这个模块的内容如果你已经非常熟悉标准库的并发原语了就可以看看扩展并发原语和分布式并发原语的内容。

同时,在学习主线层面,主要是四大步骤包括基础用法、实现原理、易错场景、知名项目中的Bug。每一个模块,我都会带着你按照这四个步骤来学习,目的就是带你熟知每一种并发原语的实现机制和适用场景。

Go中有一个大的方向就是任务编排用Channel共享资源保护用传统并发原语。在刚开始学习时你可以基于这个原则去选择相应的并发原语这是没错的。但是如果你想要在Go并发编程的道路上向前走就不能局限于这个原则。

实际上针对同一种场景也许存在很多并发原语都适用的情况但是一定是有最合适的那一个。所以你必须非常清楚每种并发原语的实现机制和适用场景千万不要被网上的一些文章误导万事皆用Channel。

而且你还可以深入学习下Go并发原语的源代码。你会发现很多独到的设计比如Mutex为了公平性考量的设计、sync.Map为提升性能做的设计以及很多并发原语的异常状况的处理方式。尤其是这些异常状况常常是并发编程中程序panic的原因。

所以,如果你能深入了解这些并发原语的实现,不但会提高你的编程能力,还能让你避免在开发中踩并发问题的坑。这个时候,你就达到精通的程度了。

如果没有做过大型并发项目你可能还不太清楚并发原语的重要性。那么我建议你先阅读一下课程中介绍的知名项目中犯的错这也是这门课里我特别设计的一部分内容。通过理解这些Go大牛们犯的错误以及解决方案你就可以积累一套避坑指南和应对之道。

有了这两条线的学习我们就从广度和深度上掌握了Go并发编程的知识点。这些是不是就足够了呢我们还可以更进一步你要有野心能够创造出自己需要的并发原语。

这里的创造有两层含义。第一层是对既有的并发原语进行组合使用两个、三个或者更多的并发原语去解决问题。比如说我们可以通过信号量和WaitGroup组合成一个新的并发原语这个并发原语可以使用有限个goroutine并发处理子任务。第二层含义是“无中生有”根据已经掌握的并发原语的设计经验创造出合适的新的并发原语以应对一些特殊的并发问题。比如说标准库中并没有信号量你可以自己创造出这个类型。

达到了这一层那就不得了了可以说你对Go并发原语的掌握已经出神入化了。那想要达到这个程度是不是很难呢确实不容易不过我相信如果你仔细学习了我们课程里的每一节课心里牢牢地锚定3个目标建立起一个丰富的并发原语库熟知每一种并发原语的实现机制和适用场景能够创造出自己需要的并发原语。达到了这3个目标你就可以轻松地应对各种并发问题了。甚至可以说你几乎能站在Go并发编程的顶端成为大牛中的一员。

最后我想说的是Go并发编程的世界确实纷繁复杂涉及到的内容非常多。你可以把它看作是一个江湖如果你想拥有极强的作战力就要拥有足够多的武器并且修炼内功。这门课就是你的修炼山洞我准备了应有尽有的宝藏等待着你来挖掘。

修炼的过程中,最好有人和你并肩而行,共同成长。欢迎你把这门课分享给你的朋友或同事,和他/她一起提升并发编程的功力。