[TOC]
FQA
起源
开启这个项目的目的是什么?
10年前Golang项目开启之时,当时的编程世界和现在是截然不同的。软件的制作通常都使用C++或者java来进行开发,那时还没有github,大多数电脑还不是多核处理器,和Visual Studio和Eclipse相比没有更高级的IDE(集成开发环境),在互联网上免费开发使用的就更不用说了.
与此同时,让我们非常懊恼的是我们需要使用我们常用的开发语言进行非常复杂的服务器软件的开发.计算机的速度已经变得非常快了,但是我们最初使用的诸如C/C++、java之类的编程语言本身并没有很大的进步. 因此,显而易见的的是计算机的多处理器会逐渐变成常态,但是编程语言在如何高效、安全的运用多处理器方面只提供了很少的支持.
我们决定退一步思考随着科技的发展在未来若干年哪些问题才是主导软件工程关键因素,并且一门新的语言该怎样帮助解决这些问题.例如,随着多核CPU的崛起争论最多的是编程语言首要应该提供并发或者并行的执行顺序方面的支持.让高并发的程序的资源管理变得更加优良,提供垃圾回收机制,至少提供一些安全的自动管理内存的机制.
这些方面的思考引出了一系列的讨论,这也直接导致了Golang的诞生,第一步是一组想法和期望,最后衍生出了一门编程语言. Go的总体目标是可以通过启用工具完成自动格式化代码的任务以及消除在大型代码库上工作的困难方面给程序员提供更多的帮助.
关于如何实现这些目标或者至少实现这些目标更多的描述在这篇文章中有更详细的描述. Go at Google: Language Design in the Service of Software Engineering.
这个项目的历史渊源是什么?
2007/09/21,Robert Griesemer, Rob Pike 和Ken Thompson三人在一块白板上起草了这门语言的蓝图.不久后这个目标变成了一个初步计划, 然后他们一边做着不相关的工作一边继续完成设计. 直到2008年1月Ken开始在编译器的方面进行了探索到一些想法是生成C代码作为它的输出.到年中的时候这个项目变成了一个全职的项目并且有足够都多的时间可以放开手脚在编译器方面做更多的尝试了.2008年5月Ian Taylor独立的起草了使用GCC作为Go的基础编译器的草案.Russ Cox在年底开始加入并且开始了让这门语言从原型到现实的变得更进一步.
2009年11月10日Go变成了一个开源软件项目. 无数的人开始在社区贡献自己的想法、在社区讨论并且贡献自己的代码.
时至今日在全世界各地有数百万的Go语言的开发者了,并且这个数据还在与日俱增. Go的成功已经远超最初的预期.
这门语言是叫做Go还是Golang?
这门语言应该称作Go. golang这个绰号的出现是因为golang.org这个官方网站.golang的大量被使用是因为golang很容易作为标签被使用.例如,在推特上的标签就是"#golang". 不管怎样这门语言的名字就是单纯的称作Go.
PS:尽管官方的logo上是两个大写的字母,但是这门语言的名字就是Go而不是GO.
为什么要创建这样一门的新的编程语言呢?
Go的诞生于我们在google的工作中使用的现有的编程语言和环境不断的产生挫败感. 编程变得非常困难,对于语言的选择也编程了一种噩梦.必须选择一种能够高效编译、执行以及易于编写的语言. 当前主流的编程语言都不能同时具备这三个特性.程序员们开始趋于选择安全性和效率都较低的动态编程语言例如Python、javaScript而不是选择C++,也有少数会选择java.
我们并不是在这些方面思考、探索的孤独者. Go只是这些年以来产生的最早的几门语言,例如Rust、Elixir、Swift等等. 这些编程语言变得非常活跃几乎开始成为了主流的编程语言了.
Go旨在尝试将动态语言的简单、易于理解和静态语言的高效、安全性相结合,同时也提供计算机网络以及多核计算方面的支持.使用Go进行工作的终极目标是足够的快. 只需要花几秒钟就能构建一个可执行文件. 为了达成这个目标我们需要解决一些语言层面的问题.实现一个足够有表达能力但是轻量的类型系统.并发和垃圾回收的支持.严格的依赖规范.
Go at Google这篇文章讨论了很多Go设计背后的细节与动机能够回答FAQ中很多的问题.
Go语言的上一代是什么呢?
Go的基本语法是类似于C语言系列的,声明、包管理方面更偏向于Pascal/Modula/Oberon类的语言,有些并发方面的想法来源于Tony Hoare的CSP. 但是Go可以说是一种全新的编程语言.这门语言的设计崇尚的是程序员在做的工作,以及怎样更好的完成这些工作.至少对于编程来说高效意味着更多的乐趣.
设计的指导原则是什么?
在Go语言被设计出来之前,Java和C++是服务器开发方面最常用的编程语言,或者可以说至少在Google是这个样子.我感觉这些语言需要大量的积累和不断的重复编写来增加语言的熟练度.因此很多程序员放弃了高效以及安全性开始选择python这样的简单易于编写的动态类型语言.我们感觉可以让一门语言将这些语言的优势结合起来.
Go尝试减少类型定义的关键词,自始至终都在尝试降低语言的复杂程度.在Go语言里面没有前置声明和头文件,任何定义都只进行一次.使用:=这样的语法快速的同时完成定义和初始化.Go语言也没有复杂的继承关系,每个类型都仅仅拥有自己的定义.
另一个重要的原则是保持(keep the concepts orthogonal). 方法可以为任何类型实现,结构体表达数据,接口表示抽象类型.
用法
Google内部在使用Go吗?
答案是肯定的,Go在Google内部的产品中被广泛使用.一个最明显的例子就是golang.org的服务器.
一个更具说服力的案例是Google的下载服务器dl.google.com,这个服务提供Chrome的二进制包下载以及类似apt-get的安装包下载.
Go的使用者已经不仅仅局限于google内部了,它已经成为了例如大数据处理等方面的主要编程语言了.
Go程序可以和C/C++链接使用吗?
将C和Go在同一个地址空间使用是可行的,但是并不是原生支持的,需要一个接口程序来实现二者的共同使用.将C和Go代码链接使用将无法使用Go提供的安全的内存以及栈的管理机制.有时候如果不能使用纯Go代码而必须使用C语言来解决某个问题时,那么应该小心的对二者结合进行使用.如果需要将二者结合使用详细请参考Go的编译器说明已经cgo相关的说明文档.
Go提供IDE吗?
Go项目不包含定制的IDE.但是这门语言的设计方向是让源代码易于被分析.因此,很多知名的IDE对Go都有很好的支持,或者通过安装插件就可以轻松的支持Go的开发.
以下这些知名的IDE或者编辑器对Go都有非常好的支持,包括Emacs、vim、vscode、atom、eclipse、sublime、intelliJ(有个定制版的叫做Goland).
Go支持Google的protobuf吗?
有一个独立的开源项目golang/protobuf提供编译插件以及代码库来提供Go在protobuf上的支持.
设计
Go有运行时吗?
Go拥有一个强大的代码库叫做runtime,runtime会作为Go程序的一部分.这个运行时代码库实现了GC、并发控制、栈管理以及实现一些其他的核心的Go语言的特性. runtime是Go语言的核心,它的地位类似于C语言中libc库.
一个非常重要的也很好理解的特性是Go的runtime不像java它并不包含虚拟机.Go程序是直接编译生成了本地机器码的.尽管runtime这个术语经常被用来描述虚拟运行环境,但是在Go的世界里并不是这样,它表达的就是一个提供Go语言核心特性支持的代码库.
为什么Go没有异常处理机制?
我们认为try-catch-finally术语来作为异常处理方式会导致代码结构变得异常复杂.这样也会鼓励程序标记大量的错误标签例如文件打开失败这种异常.
Go的错误处理使用一种新的机制,Go的多返回值的方式可以将错误当做一个返回值很轻松的报告一个错误. Go提供一个类型error来作为返回的错误类型,这一点和其他语言有一定的区别.
Go还提供了一个内置的方法来提示和恢复真正不可预测的异常情况.恢复机制会在程序出现崩溃错误的时候触发.
查看defer、panic和recover的详细说明能够帮助你快速的理解这个机制.
为什么是goroutine而不是线程?
goroutine的机制让并发编程变得更加简单.让多个独立的可执行方法,类似协程的方式,让它们在一组线程上运行这个想法已经持续很长一段时间了.如果一个协程可能因为发生了系统调用出现了阻塞的时候,运行时自动的将同一个操作系统线程切换执行其他的协程,这样就不会造成线程阻塞.编程者对此是毫无感知的,goroutine的另一个优势是拥有非常轻量的栈空间,大于只有几kb大小.
为了让goroutine的栈空间能够足够小,Go的运行时使用能够重置大小并且有上限的栈.一个新建的goroutine只会拥有几kb的很小的栈空间,大多数时候这种大小已经足够了.当这个栈大小不够用时运行时会自动的适当的进行扩增或者收缩.
根据实践测试可以创建成千上万的goroutine,如果goroutine就是线程的话,那么操作系统资源早就被耗尽了.
为什么map的操作没有被定义为原子的?
经过长时间的讨论最后认为的map的应用不需要保证多goroutine访问安全,map的通常用法会被当做一个更大的结构体的成员,而这个结构体通常已经使用了其他的同步机制.因为如果每一个map的操作都需要使用一个互斥锁将会大大的拖累程序的性能.这是一个很艰难的决定,这意味着如果没有正确的控制map的访问会导致程序的崩溃.
map的不安全的访问只会在更新的时候发生.如果所有的goroutine都只进行查找元素或者使用for-range这种迭代读取访问,这种情况下对map的并发访问在没有同步操作的基础上也是安全的.
为了辅助map的运用,有些语言的实现会提供一个自动检查的方式来报告非安全的并发访问map.
类型
Go是面向对象的编程语言吗?
是也不是. 尽管Go有类型和方法也允许面向对象风格的编程,但是Go中没有类型继承关系.Go中interface(接口)的概念提供一个不同的实现方式,我们坚信这个方法将会更加易于使用.还有一种方式是不同于子类的镶嵌的组合方式.Go中的方法会比c++或者java的函数使用的更加广泛,Go允许给任何类型定义方法甚至是内建的类型.
因此缺少继承关系,Go中的对象会显得比C++或者Java中的对象显得更加轻量.
为什么Go中没有类型继承呢?
关于面向对象的编程,至少在最著名的编程语言中都包含了大量关于类型与类型的关系的讨论,这种关系一般都可以自动派生.然而Go使用了一种截然不同的方式.
Go不需要提前定义两个类型的关系,Go实现任何一个interface的方法则自动满足这个interface. 所以Go的类型可以适应任何一个interface从而避免了使用复杂的多重继承关系.
我能将[]T转换为[]interface吗?
不能直接进行转换.从语言层面来说这个转换时不允许的,因为他们拥有不同的内存结构.这个转换需要将每个元素进行拷贝,例如将一个int的切片转换为一个interface切片
1 2 3 4 5 |
t := []int{1, 2, 3, 4} s := make([]interface{}, len(t)) for i, v := range t { s[i] = v } |
如果T1和T2底层类型相同能够将[]T1转换为[]T2吗?
下面代码中最后一行是无法通过编译的
1 2 3 4 5 6 |
type T1 int type T2 int var t1 T1 var x = T2(t1) // OK var st1 []T1 var sx = ([]T2)(st1) // NOT OK |
编写代码
怎么查看代码库文档?
Go编写的程序godoc能够通过程序的定义、注释等相关信息生成一个web页面提供对应的文档说明,事实上golang.org上的文档就是使用godoc生成的.
指针和内存分配?
什么时候方法(function)的参数用值传递?
在所有的类C语言中,Go语言中的所有方法中的参数传递都是值传递. 因此,一个方法拿到的永远都是这个参数的副本值.例如传递一个int型的参数给一个方法,就会构造一个int的复制. 如果传递一个指针,那么方法拿到的也是构造的一个指针的副本.但是指向的数据并不是副本,他们指向同一个数据地址.
map和slice类型的参数值类似于传指针的方式. 他们都是底层map或者slice的一个映射.拷贝一个map或者slice并没有拷贝他们指向的数据.拷贝一个interface会拷贝这个interface的值,如果这个interface是一个结构体类型.如果一个interface包含一个指针类型的值,那么发生拷贝时拷贝的也只指针值,并没有拷贝真正的数据.
什么时候可以使用一个指向interface的指针?
永远都不要这么做.
方法应该定义在结构体的值还是指针?
1 2 |
func (s *MyStruct) pointerMethod() { } // method on pointer func (s MyStruct) valueMethod() { } // method on value |
new和make有什么不同?
简单来说:new分配内存,make会对slice、map、channel类型进行初始化.
怎样知道变量的内存是分配在栈上还是在堆上?
从正确的角度来说,你不需要知道这件事.
为什么我的Go进程使用那么多的虚拟内存?
Go的内存分配器会储备一个很大的虚拟地址区域来用于内存分配.
并发
为什么我的程序没有在多CPU环境下运行的更快?
一个程序是否会因为多CPU而运行得更快取决于这个程序在解决什么问题. Go提供很多并发原语例如Goroutine、chan但是并发的本质是底层是否能够并行.如果底层运算本质上是线性的那么CPU的数量完全不影响程序的运行速度.