摘要:本文从Go的语法,类型系统,编码风格,语言工具,编码工具和使用案例等几方面对Go语言进行了学习和探讨。
Go语言发布以后,不少公司特别是云厂商也开始用Go语言重构产品的基础架构,并且不少企业都是直接采用Go语言进行开发,最近热火朝天的Docker就是采用Go语言进行开发的。本文咱们一块儿来探讨和学习一下Go语言的技术特色。先来看个例子:html
package main import ( "fmt" "time" ) // 要在goroutine中运行的函数。done通道将被用来通知工做已经完成。 func worker(done chan bool) { fmt.Print("working...") time.Sleep(time.Second) fmt.Println("done") // 通知完成。 done <- true } func main() { // 建立一个通道 done := make(chan bool, 1) go worker(done) // 等待done变为true <-done }
上例中是一个在Go语言中使用goroutine和通道的例子。 其中:git
go 关键字是用来启动一个goroutine程序员
done <- true, 向通道传值github
<-done, 读取通道值golang
Go是由RobertGriesemer、RobPike和KenThompson在Google设计的一种静态类型化的、须编译后才能运行的编程语言。web
Go在语法上相似于C语言,但它具备C语言没有的优点,如内存安全、垃圾回收、结构化的类型和CSP风格的并发性。算法
它的域名是http://golang.org,因此一般被称为"Golang",但正确的名称是Go。数据库
Go的设计受C语言的影响,但更加简单和安全。该语言包括以下特色:npm
Go的语法包含C语言中保持代码简洁性和可读性的语法特色。编程
引入了一个联合声明/初始化操做符,容许程序员写出i := 3或s :="Hello, world!",而不须要指定使用的变量类型。
这与C语言中的int i= 3; 和 const char *s = "Hello, world!";造成鲜明对比。
分号仍然是终止语句,但在行结束时是隐含的。
在Go中,一个函数方法能够返回多个值,返回一个结果和错误err组合对是向调用者提示错误的常规方式。
Go的范围表达式容许在数组、动态数组、字符串、字典和通道上进行简洁的迭代,在C语言中,有三种循环来实现这个功能。
Go有许多内置的类型,包括数字类型(byte、int6四、float32等)、booleans和字符串(string)。
字符串是不可更改的。
内置的运算符和关键字(而不是函数)提供了串联、比较和UTF-8编码/解码。
记录类型能够用struct关键字定义。
对于每一个类型T和每一个非负整数常数n,都有一个数组类型,表示为[n]T,所以,不一样长度的数组有不一样的类型。
动态数组能够做为"Slice"使用,如对于某类型T,表示为[]T。这些数组有一个长度和一个容量,容量规定了什么时候须要分配新的内存来扩展数组。若干个Slice能够共享它们的底层内存。
全部类型均可以定义指针, T类型的指针可定义为T。地址抽取和隐式访问使用&和操做符,这跟C语言同样,或者隐式的经过方法调用或属性访问使用。
除了标准库中的特殊的unsafe.Pointer类型,通常指针没有指针运算。
对于一个组合对类型K、V,类型map[K]V是将类型K键映射到类型V值的哈希表的类型。
chan T是一个通道,容许在并发的Go进程之间发送T类型的值。
除了对接口的支持外,Go的类型系统是显示的:类型关键字能够用来定义一个新的命名类型,它与其余具备相同布局的命名类型(对于结构体来讲,相同的成员按相同的顺序排列)不一样。类型之间的一些转换(如各类整数类型之间的转换)是预先定义好的,添加一个新的类型能够定义额外的转换,但命名类型之间的转换必须始终显式调用。例如,类型关键字能够用来定义IPv4地址的类型,基于32位无符号整数:
type ipv4addr uint32
经过这个类型定义,ipv4addr(x)将uint32值x解释为IP地址。若是简单地将x分配给类型为ipv4addr的变量将会是一个类型错误。
常量表达式既能够是类型化的,也能够是 "非类型化的";若是它们所表明的值经过了编译时的检查,那么当它们被分配给一个类型化的变量时,就会被赋予一个类型。
函数类型由func关键字表示;它们取0个或更多的参数并返回0个或更多的值,这些值都是要声明类型的。
参数和返回值决定了一个函数的类型;好比,func(string, int32)(int, error)就是输入一个字符串和一个32位有符号的整数,并返回一个有符号的整数和一个错误(内置接口类型)的值的函数类型。
任何命名的类型都有一个与之相关联的方法集合。上面的IP地址例子能够用一个检查其值是否为已知标准的方法来扩展:
// ZeroBroadcast报告addr是否为255.255.255.255.255。 func (addr ipv4addr) ZeroBroadcast() bool { return addr == 0xFFFFFFFF }
以上的函数在ipv4addr上增长了一个方法,但这个方法在uint32上没有。
Go提供了两个功能来取代类继承。
首先是嵌入方法,能够当作是一种自动化的构成形式或委托代理。
第二种是接口,它提供了运行时的多态性。
接口是一类型,它在Go的类型系统中提供了一种有限的结构类型化形式。
一个接口类型的对象同时也有另外一种类型的定义对应,这点就像C++对象同时具备基类和派生类的特征同样。
Go接口是在Smalltalk编程语言的协议基础上设计的。
在描述Go接口时使用了鸭式填充这个术语。
虽然鸭式填充这个术语没有精确的定义,它一般是说这些对象的类型一致性没有被静态检查。
因为Go接口的一致性是由Go编译器静态地检查的,因此Go的做者们更喜欢使用结构类型化这个词。
接口类型的定义按名称和类型列出了所需的方法。任何存在与接口类型I的所需方法匹配的函数的T类型的对象也是类型I的对象。类型T的定义不须要也不能识别类型I。例如,若是Shape、Square和Circle被定义为:
import "math" type Shape interface { Area() float64 } type Square struct { // 注:没有 "实现 "声明 side float64 } func (sq Square) Area() float64 { return sq.side * sq.side } type Circle struct { // 这里也没有 "实现 "声明 radius float64 } func (c Circle) Area() float64 { return math.Pi * math.Pow(c.radius, 2) }
一个正方形和一个圆都隐含着一个形状(Shape)类型,而且能够被分配给一个形状(Shape)类型的变量。
Go的接口系统使用了告终构类型。接口也能够嵌入其余接口,其效果是建立一个组合接口,而这个组合接口正是由实现嵌入接口的类型和新定义的接口所增长的方法来知足的。
Go标准库在多个地方使用接口来提供通用性,这包括基于Reader和Writer概念的输入输出系统。
除了经过接口调用方法,Go还容许经过运行时类型检查将接口值转换为其余类型。这就是类型断言和类型切换。
空接口{}是一个重要的基本状况,由于它能够引用任何类型的选项。它相似于Java或C#中的Object类,能够知足任何类型,包括像int这样的内置类型。
使用空接口的代码不能简单地在被引用的对象上调用方法或内置操做符,但它能够存储interface{}值,经过类型断言或类型切换尝试将其转换为更有用的类型,或者用Go的reflect包来检查它。
由于 interface{} 能够引用任何值,因此它是一种摆脱静态类型化限制的有效方式,就像C 语言中的 void*,但在运行时会有额外的类型检查。
接口值是使用指向数据的指针和第二个指向运行时类型信息的指针来实现的。与Go中其余一些使用指针实现的类型同样,若是未初始化,接口值是零。
在Go的包系统中,每一个包都有一个路径(如"compress/bzip2 "或"http://golang.org/x/net/html")和一个名称(如bzip2或html)。
对其余包的定义的引用必须始终以其余包的名称做为前缀,而且只有其余包的大写的名称才能被访问:io.Reader是公开的,但bzip2.reader不是。
go get命令能够检索存储在远程资源库中的包,鼓励开发者在开发包时,在与源资源库相对应的基础路径
(如http://example.com/user_name/...)内开发程序包,从而减小未来在标准库或其余外部库中名称碰撞的可能性。
有人提议Go引入一个合适的包管理解决方案,相似于CPANfor Perl或Rust的Cargo系统或Node的npm系统。
在计算机科学中,通讯顺序过程(communicating sequential processes,CSP)是一种描述并发系统中交互模式的正式语言,它是并发数学理论家族中的一个成员,被称为过程算法(process algebras),或者说过程计算(process calculate),是基于消息的通道传递的数学理论。
CSP在设计Oceam编程语言时起了很大的影响,同时也影响了Limbo、RaftLib、Go、Crystal和Clojure的core.async等编程语言的设计。
CSP最先是由TonyHoare在1978年的一篇论文中描述的,后来有了很大的发展。
CSP做为一种工具被实际应用于工业上,用于指定和验证各类不一样系统的并发功能,如T9000Transputer以及安全的电子商务系统。
CSP自己的理论目前也仍然是被积极研究的对象,包括增长其实际适用范围的工做,如增长可分析的系统规模。
Go语言有内置的机制和库支持来编写并发程序。并发不只指的是CPU的并行性,还指的是异步性处理:让相对慢的操做,如数据库或网络读取等操做在作其余工做的同时运行,这在基于事件的服务器中很常见。
主要的并发构造是goroutine,这是一种轻量级处理类型。一个以go关键字为前缀的函数调用会在一个新的goroutine中启动这个函数。
语言规范并无指定如何实现goroutine,但目前的实现将Go进程的goroutine复用到一个较小的操做系统线程集上,相似于Erlang中的调度。
虽然一个标准的库包具备大多数经典的并发控制结构(mutex锁等),但Go并发程序更偏重于通道,它提供了goroutines之间的消息传功能。
可选的缓冲区以FIFO顺序存储消息,容许发送的goroutines在收到消息以前继续进行。
通道是类型化的,因此chan T类型的通道只能用于传输T类型的消息。
特殊语法约定用于对它们进行操做;<-ch是一个表达式,它使执行中的goroutine在通道ch上阻塞,直到有一个值进来,而ch<- x则是发送值x(可能阻塞直到另外一个goroutine接收到这个值)。
内置的相似于开关的选择语句能够用来实现多通道上的非阻塞通讯。Go有一个内存模型,描述了goroutine必须如何使用通道或其余操做来安全地共享数据。
通道的存在使Go有别于像Erlang这样的actor模型式的并发语言,在这种语言中,消息是直接面向actor(对应于goroutine)的。在Go中,能够经过在goroutine和通道之间保持一对一的对应关系来,Go语言也容许多个goroutine共享一个通道,或者一个goroutine在多个通道上发送和接收消息。
经过这些功能,人们能够构建像workerpools、流水线(好比说,在下载文件时,对文件进行解压缩和解析)、带超时的后台调用、对一组服务的"扇出"并行调用等并发构造。
通道也有一些超越进程间通讯的常规概念的用途,好比做为一个并发安全的回收缓冲区列表,实现coroutines和实现迭代器。
Go的并发相关的结构约定(通道和替代通道输入)来自于TonyHoare的通讯顺序进程模型。
不像之前的并发编程语言,如Occam或Limbo(Go的共同设计者RobPike曾在此基础上工做过的语言),Go没有提供任何内置的安全或可验证的并发概念。
虽然在Go中,上述的通讯处理模型是推荐使用的,但不是惟一的:一个程序中的全部goroutines共享一个单一的地址空间。这意味着可突变对象和指针能够在goroutines之间共享。
有一项研究比较了一个不熟悉Go语言的老练程序员编写的程序的大小(以代码行数为单位)和速度,以及一个Go专家(来自Google开发团队)对这些程序的修正,对Chapel、Cilk和IntelTBB作了一样的研究。
研究发现,非专家倾向于用每一个递归中的一条Go语句来写分解-解决算法,而专家则用每一个处理器的一条Go语句来写分布式工做同步程序。Go专家的程序一般更快,但也更长。
Goroutine对于如何访问共享数据没有限制,这使得条件竞赛成为可能的问题。
具体来讲,除非程序经过通道或其余方式显式同步,不然多个goroutine共享读写一个内存区域可能会发生问题。
此外,Go的内部数据结构,如接口值、动态数组头、哈希表和字符串头等内部数据结构也不能幸免于条件竞赛,所以在多线程程序中,若是修改这些类型的共享实例没有同步,就会存在影响类型和内存安全的状况。
gc工具链中的连接器默认会建立静态连接的二进制文件,所以全部的Go二进制文件都包括Go运行所须要的内容。
Go故意省略了其余语言中常见的一些功能,包括继承、通用编程、断言、指针运算、隐式类型转换、无标记的联合和标记联合。
Go做者在Go程序的风格方面付出了大量的努力:
主要的Go发行版包括构建、测试和分析代码的工具。
go build,它只使用源文件中的信息来构建Go二进制文件,不使用单独的makefiles。
gotest,用于单元测试和微基准
go fmt,用于格式化代码
go get,用于检索和安装远程包。
go vet,静态分析器,查找代码中的潜在错误。
go run,构建和执行代码的快捷方式
godoc,用于显示文档或经过HTTP
gorename,用于以类型安全的方式重命名变量、函数等。
go generate,一个标准的调用代码生成器的方法。
它还包括分析和调试支持、运行时诊断(例如,跟踪垃圾收集暂停)和条件竞赛测试器。
第三方工具的生态系统加强了标准的发布系统,如:
gocode,它能够在许多文本编辑器中自动完成代码,
goimports(由Go团队成员提供),它能够根据须要自动添加/删除包导入,以及errcheck,它能够检测可能无心中被忽略的错误代码。
流行的Go代码工具:
GoLand:JetBrains公司的IDE。
VisualStudio Code
LiteIDE:一个"简单、开源、跨平台的GoIDE"
Vim:用户能够安装插件:
vim-go
用Go编写的一些著名的开源应用包括:
Caddy,一个开源的HTTP/2web服务器,具备自动HTTPS功能。
CockroachDB,一个开源的、可生存的、强一致性、可扩展的SQL数据库。
Docker,一套用于部署Linux容器的工具。
Ethereum,以太币虚拟机区块链的Go-Ethereum实现。
Hugo,一个静态网站生成器
InfluxDB,一个专门用于处理高可用性和高性能要求的时间序列数据的开源数据库。
InterPlanetaryFile System,一个可内容寻址、点对点的超媒体协议。
Juju,由UbuntuLinux的包装商Canonical公司推出的服务协调工具。
Kubernetes容器管理系统
lnd,比特币闪电网络的实现。
Mattermost,一个团队聊天系统
NATSMessaging,是一个开源的消息传递系统,其核心设计原则是性能、可扩展性和易用性。
OpenShift,云计算服务平台
Snappy,一个由Canonical开发的UbuntuTouch软件包管理器。
Syncthing,一个开源的文件同步客户端/服务器应用程序。
Terraform,是HashiCorp公司的一款开源的多云基础设施配置工具。
其余使用Go的知名公司和网站包括:
Cacoo,使用Go和gRPC渲染用户仪表板页面和微服务。
Chango,程序化广告公司,在其实时竞价系统中使用Go。
CloudFoundry,平台即服务系统
Cloudflare,三角编码代理Railgun,分布式DNS服务,以及密码学、日志、流处理和访问SPDY网站的工具。
容器Linux(原CoreOS),是一个基于Linux的操做系统,使用Docker容器和rkt容器。
Couchbase、Couchbase服务器内的查询和索引服务。
Dropbox,将部分关键组件从Python迁移到了Go。
谷歌,许多项目,特别是下载服务器http://dl.google.com。
Heroku,Doozer,一个提供锁具服务的公司
HyperledgerFabric,一个开源的企业级分布式分类帐项目。
MongoDB,管理MongoDB实例的工具。
Netflix的服务器架构的两个部分。
Nutanix,用于其企业云操做系统中的各类微服务。
Plug.dj,一个互动式在线社交音乐流媒体网站。
SendGrid是一家位于科罗拉多州博尔德市的事务性电子邮件发送和管理服务。
SoundCloud,"几十个系统"
Splice,其在线音乐协做平台的整个后端(API和解析器)。
ThoughtWorks,持续传递和即时信息的工具和应用(CoyIM)。
Twitch,他们基于IRC的聊天系统(从Python移植过来的)。
Uber,处理大量基于地理信息的查询。
package main import "fmt" func main() { fmt.Println("Hello, world!") }
package main import ( "fmt" "time" ) func readword(ch chan string) { fmt.Println("Type a word, then hit Enter.") var word string fmt.Scanf("%s", &word) ch <- word } func timeout(t chan bool) { time.Sleep(5 * time.Second) t <- false } func main() { t := make(chan bool) go timeout(t) ch := make(chan string) go readword(ch) select { case word := <-ch: fmt.Println("Received", word) case <-t: fmt.Println("Timeout.") } }
没有测试的代码是不完整的,所以咱们须要看看代码测试部分的编写。
代码:
func ExtractUsername(email string) string { at := strings.Index(email, "@") return email[:at] }
测试案例:
func TestExtractUsername(t *testing.T) { type args struct { email string } tests := []struct { name string args args want string }{ {"withoutDot", args{email: "r@google.com"}, "r"}, {"withDot", args{email: "jonh.smith@example.com"}, "jonh.smith"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := ExtractUsername(tt.args.email); got != tt.want { t.Errorf("ExtractUsername() = %v, want %v", got, tt.want) } }) } }
接下来我写一个例子建立REST API后端服务:
咱们的服务提供以下的API:
### GET http://localhost:10000/ ### GET http://localhost:10000/all ### GET http://localhost:10000/article/1 ### POST http://localhost:10000/article HTTP/1.1 { "Id": "3", "Title": "Hello 2", "desc": "Article Description", "content": "Article Content" } ### PUT http://localhost:10000/article HTTP/1.1 { "Id": "2", "Title": "Hello 2 Update", "desc": "Article Description Update", "content": "Article Content Update" }
完整代码:
package main import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "github.com/gorilla/mux" ) type Article struct { Id string `json:"Id"` Title string `json:"Title"` Desc string `json:"desc"` Content string `json:"content"` } var MapArticles map[string]Article var Articles []Article func returnAllArticles(w http.ResponseWriter, r *http.Request) { fmt.Println("Endpoint Hit: returnAllArticles") json.NewEncoder(w).Encode(Articles) } func homePage(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Welcome to the HomePage!") fmt.Println("Endpoint Hit: homePage") } func createNewArticle(w http.ResponseWriter, r *http.Request) { reqBody, _ := ioutil.ReadAll(r.Body) var article Article json.Unmarshal(reqBody, &article) Articles = append(Articles, article) MapArticles[article.Id] = article json.NewEncoder(w).Encode(article) } func updateArticle(w http.ResponseWriter, r *http.Request) { reqBody, _ := ioutil.ReadAll(r.Body) var article Article json.Unmarshal(reqBody, &article) found := false for index, v := range Articles { if v.Id == article.Id { // Found! found = true Articles[index] = article } } if !found { Articles = append(Articles, article) } MapArticles[article.Id] = article json.NewEncoder(w).Encode(article) } func returnSingleArticle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) key := vars["id"] fmt.Fprintf(w, "Key: %s n", key) json.NewEncoder(w).Encode(MapArticles[key]) } func handleRequests() { myRouter := mux.NewRouter().StrictSlash(true) myRouter.HandleFunc("/", homePage) myRouter.HandleFunc("/all", returnAllArticles) myRouter.HandleFunc("/article", createNewArticle).Methods("POST") myRouter.HandleFunc("/article", updateArticle).Methods("PUT") myRouter.HandleFunc("/article/{id}", returnSingleArticle) log.Fatal(http.ListenAndServe(":10000", myRouter)) } func main() { fmt.Println("Rest API is ready ...") MapArticles = make(map[string]Article) Articles = []Article{ Article{Id: "1", Title: "Hello", Desc: "Article Description", Content: "Article Content"}, Article{Id: "2", Title: "Hello 2", Desc: "Article Description", Content: "Article Content"}, } for _, a := range Articles { MapArticles[a.Id] = a } handleRequests() }
调用添加,更新API之后返回全部数据的测试结果:
MicheleSimionato对Go大加赞赏:
接口系统简洁,并刻意省略了继承。
EngineYard的DaveAstels写道:
Go是很是容易上手的。不多的基本语言概念,语法也很干净,设计得很清晰。Go目前仍是实验性的,还有些地方比较粗糙。
2009年,Go被TIOBE编程社区指数评选为年度最佳编程语言。
到2010年1月,Go的排名达到了第13位,超过了Pascal等成熟的语言。
但到了2015年6月,它的排名跌至第50位如下,低于COBOL和Fortran。
但截至2017年1月,它的排名又飙升至第13位,显示出它的普及率和采用率有了显著的增加。
Go被评为2016年TIOBE年度最佳编程语言。
BruceEckel曾表示:
C++的复杂性(在新的C++中甚至增长了更多的复杂性),以及由此带来的对生产力的影响,已经没有任何理由继续使用C++了。C++程序员为了克服C语言的一些问题而作出的加强初衷目前已经没有了意义,而Go此时显得更有意义。
2011年一位Google工程师R.Hundt对Go语言及其GC实现与C++(GCC)、Java和Scala的对比评估发现。
Go提供了有趣的语言特性,这也使得Go语言有了简洁、标准化的特征。这种语言的编译器还不成熟,这在性能和二进制大小上都有体现。
这一评价收到了Go开发团队的快速反应。
IanLance Taylor由于Hundt的评论改进了Go代码;
RussCox随后对Go代码以及C++代码进行了优化,并让Go代码的运行速度比C++略快,比评论中使用的代码性能快了一个数量级以上。
2009年11月10日,也就是Go!编程语言全面发布的当天,Go!编程语言的开发者FrancisMcCabe(注意是感叹号)要求更改Google的语言名称,以免与他花了10年时间开发的语言混淆。
McCabe表示了对谷歌这个'大块头'最终会碾压他"的担心,这种担心引发了120多名开发者的共鸣,他们在Google官方的问题线程上评论说他们应该更名,有些人甚至说这个问题违背了Google的座右铭:"不要做恶。"
2010年10月12日,谷歌开发者RussCox关闭了这个问题,自定义状态为"不幸",并附上了如下评论:
"有不少计算产品和服务都被命名为Go。在咱们发布以来的11个月里,这两种语言的混淆度极低。"
Go的批评家们的观点:
本文从Go的语法,类型系统,编码风格,语言工具,编码工具和使用案例等几方面对Go语言进行了学习和探讨,但愿能够抛砖引玉,对Go语言感兴趣的同仁有所裨益。