go 语言火了有很久了,一直也没有认真的学习,刚好赶上最近的空档期,完整了学习了一遍。
肯定是谈不上深入了,不过对其也有了大致全面的认识,也有比较多的感触,所以有了这篇文章。
前言
在动手记录这篇文章时,我并没有实际的 go 语言开发经历,只是阅读了两本书,结合了自己的理解,记录下的这些感悟。
应该会有不少理解不到位,甚至南辕北辙的点,这些留到以后来纠正吧。
编程语言的设计,是抽象和折中的艺术
首先,最大的感悟就是这句话,应该是原创,吃饭的时候自己悟出来的一句话。
抽象非常重要,对于要用这门语言来解决的问题,语言层面合适的抽象,会让语言的使用者非常舒服。
比如,很多领域内的小语言,DSL,会让使用者感觉非常自然。
当然,抽象也并不是没有代价的,这种时候就考验折中的能力了。
GMP
GMP 模型,是 go 对并发模型的抽象,确实挺好的,虽然 主要 也是协作式的调度方式,但是抽象到了语言/标准库层面,所以用起来更自然一些,而且还一定程度的屏蔽了多线程的并发,对于构建高并发业务系统,确实挺方便的。
G 表示协程,M 表示线程,P 表示逻辑处理器;仔细想想确实很巧。
协程
首先要拼高并发,肯定得是用户态协程,这是很多语言早就实践过的。
go 语言的协程,比操作系统线程肯定是要轻量很多,不过还是相对 Lua 的协程而言,还是要略重一些的。
一个 Goroutine 的初始 stack size 是 2K,Lua coroutine 的 stack size 只要几百个字节。
调度
为了很自然的将一些正在阻塞的协程挂起,go 语言有一个设计精巧,也比较重的协程调度功能。
而且还实现了抢占式的协程调度,功能确实很棒,不过应该也是比较重的实现,以后找时间仔细分析下性能。
简而言之,就是将重要的 G 分配到 P 上去运行,然后 P 则依托一个实际的 M 来运行。
至于具体实现上,有一些很重要的优化,比如每个 P 都有本地队列,都是为了让这个关键的 scheduler 可以运行的更快。
通道
语言层面抽象出来的,协程间的通讯机制,这个可以很大程度的屏蔽了跨进程通讯的事实。很大程度的降低了程序员的心智负担。
GMP + CPS 感觉真的很棒。
接口
go 抽象了接口机制,对我而言,感觉还挺新颖的。
等后面动手实践的时候,好好体验下的。
GC
go 作为静态语言,还提供了垃圾回收机制,很早以前的时候没有想明白,感觉怪怪的。
虽然后来也好像慢慢懂了点,不过也没有去细想,现在感觉应该是懂了,不会觉得怪异了。
以我的理解,核心就两个点:
- 引入 GC 机制,不再需要显式的释放内存,是可以降低程序员管理内存的心智负担
- 只要 GC 对象之间的引用关系,就可以完成垃圾回收;指针也可以产生一个引用关系。
另外,go 语言将小对象,直接放到栈上面,也是挺新颖的一个搞法,比较好的减少了临时小对象数量。对于垃圾回收器而言,大量的临时小对象,也是一个很重的负担。
印象中,好像其他新语言,也会有类似这样的搞法,感觉是个好路子。
似曾相识
在学习 go 语言的时候,有些地方让我感觉似曾相识。
打个岔,感慨一下,人类的进步就是这么一步步的往前拱:站在巨人的肩膀上,再上一些自己的创新。
举几个例子:
- CPU profile 采样频率是 100HZ
- 默认的 GC 启动条件是当前 GC size 达到上一次完成后的 GC size 的两倍
其他
语言的设计仅仅是偏理论上的一面,语言的实现功底也是决定成功的重要一环,后面有计划学习下 go 语言的源码。
一个语言想要成功,除了设计要足够好,还需要长期持续的投入,即使有眼前的苟且,也需要让使用者能看到远方的未来。这个时候还是得感慨一下,有 Google 支持的巨大优势了。