我们为什么要学习Go
其实编程语言已经非常多,偏性能敏感的编译型语言有 C、C++、Java、C#、Delphi和Objective-C 等,偏快速业务开发的动态解析型语言有PHP、Python、Perl、Ruby、JavaScript和Lua等,面向特 定领域的语言有Erlang、R和MATLAB等,那么我们为什么需要 Go这样一门新语言呢? 在2000年前的单机时代,C语言是编程之王。随着机器性能的提升、软件规模与复杂度的提 高,Java逐步取代了C的位置。尽管看起来Java已经深获人心,但Java编程的体验并未尽如人意。
Go语言官方自称,之所以开发Go 语言,是因为“近10年来开发程序之难让我们有点沮丧”。 这一定位暗示了Go语言希望取代C和Java的地位,成为最流行的通用开发语言。 Go希望成为互联网时代的C语言。多数系统级语言(包括Java和C#)的根本编程哲学来源于 C++,将C++的面向对象进一步发扬光大。但是Go语言的设计者却有不同的看法,他们认为C++ 真 的没啥好学的,值得学习的是C语言。C语言经久不衰的根源是它足够简单。因此,Go语言也要 足够简单! 那么,互联网时代的C语言需要考虑哪些关键问题呢? 首先,并行与分布式支持。多核化和集群化是互联网时代的典型特征。作为一个互联网时代 的C语言,必须要让这门语言操作多核计算机与计算机集群如同操作单机一样容易。 其次,软件工程支持。工程规模不断扩大是产业发展的必然趋势。单机时代语言可以只关心 问题本身的解决,而互联网时代的C语言还需要考虑软件品质保障和团队协作相关的话题。 最后,编程哲学的重塑。计算机软件经历了数十年的发展,形成了面向对象等多种学术流派。 什么才是最佳的编程实践?作为互联网时代的C语言,需要回答这个问题。 接下来我们来聊聊Go语言在这些话题上是如何应对的
并发与分布式
多核化和集群化是互联网时代的典型特征,那语言需要哪些特性来应对这些特征呢? 第一个话题是并发执行的“执行体”。执行体是个抽象的概念,在操作系统层面有多个概念 与之对应,比如操作系统自己掌管的进程(process)、进程内的线程(thread)以及进程内的协程 (coroutine,也叫轻量级线程)。多数语言在语法层面并不直接支持协程,而通过库的方式支持的 协程的功能也并不完整,比如仅仅提供协程的创建、销毁与切换等能力。如果在这样的协程中调 用一个同步IO操作,比如网络通信、本地文件读写,都会阻塞其他的并发执行协程,从而无法真 正达到协程本身期望达到的目标。 Go语言在语言级别支持协程,叫goroutine。Go语言标准库提供的所有系统调用(syscall)操 作,当然也包括所有同步IO操作,都会出让CPU给其他goroutine,这让事情变得非常简单。我们 对比一下Java和Go,近距离观摩下两者对“执行体”的支持。 为了简化,我们在样例中使用的是Java标准库中的线程,而不是协程,具体代码如下
public class MyThread implements Runnable {
String arg;
public MyThread(String a) {
arg = a;
}
public void run() {
// ...
}
public static void main(String[] args) {
new Thread(new MyThread("test")).start();
// ...
}
}
相同功能的代码,在Go语言中是这样的:
func run(arg string) {
// ...
}
func main() {
go run("test")
...
}
对比非常鲜明。
第二个话题是“执行体间的通信”。
执行体间的通信包含几个方式:
执行体之间的互斥与同步
执行体之间的消息传递
先说“执行体之间的互斥与同步”。当执行体之间存在共享资源(一般是共享内存)时,为保证内存访问逻辑的确定性,需要对访问该共享资源的相关执行体进行互斥。当多个执行体之间 的逻辑存在时序上的依赖时,也往往需要在执行体之间进行同步。互斥与同步是执行体间最基础 的交互方式。
多数语言在库层面提供了线程间的互斥与同步支持,那么协程之间的互斥与同步呢?呃,不 好意思,没有。事实上多数语言标准库中连协程都是看不到的。
再说“执行体之间的消息传递”。在并发编程模型的选择上,有两个流派,一个是共享内存 模型,一个是消息传递模型。多数传统语言选择了前者,少数语言选择后者,其中选择“消息传 递模型”的最典型代表是Erlang语言。业界有专门的术语叫“Erlang风格的并发模型”,其主体思 想是两点:一是“轻量级的进程(Erlang中‘进程’这个术语就是我们上面说的‘执行体’)”,二 是“消息乃进程间通信的唯一方式”。当执行体之间需要相互传递消息时,通常需要基于一个消 息队列(message queue)或者进程邮箱(process mail box)这样的设施进行通信。
Go语言推荐采用“Erlang风格的并发模型”的编程范式,尽管传统的“共享内存模型”仍然 被保留,允许适度地使用。在Go语言中内置了消息队列的支持,只不过它叫通道(channel)。两 个goroutine之间可以通过通道来进行交互。