gopher是一种生活在加拿大的小动物,go的吉祥物就是这个小动物, 它的中文名叫做囊地鼠,他们最大的特点就是挖洞速度特别快,当然可能不止是挖洞啦。
Go 编程语言是Google公司的一个开源项目,它使程序员更具生产力。Go 语言具有很强的表达能力,它简洁、清晰而高效。得益于其并发机制, 用它编写的程序能够非常有效地利用多核与联网的计算机,其新颖的类型系统则使程序结构变得灵活而模块化。 Go 代码编译成机器码不仅非常迅速,还具有方便的垃圾收集机制和强大的运行时反射机制。 它是一个快速的、静态类型的编译型语言,感觉却像动态类型的解释型语言。(摘取自官网)
Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是兼具Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性。
Go语言是编程语言设计的又一次尝试,是对类C语言的重大改进,它不但能让你访问底层操作系统,还提供了强大的网络编程和并发编程支持。Go语言的用途众多,可以进行网络编程、系统编程、并发编程、分布式编程。
此外,很多重要的开源项目都是使用Go语言开发的,其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。
Less can be more
Go语言在并发编程方面比绝大多数语言要简洁不少,这一点是其最大亮点之一,也是其在未来进入高并发高性能场景的重要筹码。
不同于传统的多进程或多线程,golang的并发执行单元是一种称为goroutine
的协程。
由于在共享数据场景中会用到锁,再加上GC,其并发性能有时不如异步复用IO模型,因此相对于大多数语言来说,golang的并发编程简单比并发性能更具卖点。
在当今这个多核时代,并发编程的意义不言而喻。当然,很多语言都支持多线程、多进程编程,但遗憾的是,实现和控制起来并不是那么令人感觉轻松和愉悦。Golang不同的是,语言级别支持协程goroutine
并发(协程又称微线程,比线程更轻量、开销更小,性能更高),操作起来非常简单,语言级别提供关键字go
用于启动协程,并且在同一台机器上可以启动成千上万个协程。协程经常被理解为轻量级线程,一个线程可以包含多个协程,共享堆不共享栈。协程间一般由应用程序显式实现调度,上下文切换无需下到内核层,高效不少。协程间一般不做同步通讯,而golang中实现协程间通讯有两种:1、共享内存型,即使用全局变量加mutex
锁来实现数据共享;2、消息传递型,即使用一种独有的channel
机制进行异步通讯。
对比Java的多线程和GO的协程实现,明显更直接、简单。这就是GO的魅力所在,以简单、高效的方式解决问题,关键字go
,或许就是GO语言最重要的标志。
从C到C++,从程序性能的角度来考虑,这两种语言允许程序员自己管理内存,包括内存的申请和释放等。因为没有垃圾回收机制所以C/C++运行起来速度很快,但是随着而来的是程序员对内存使用上的很谨小慎微的考虑。因为哪怕一点不小心就可能会导致“内存泄露”使得资源浪费或者“野指针”使得程序崩溃等,尽管C++11后来使用了智能指针的概念,但是程序员仍然需要很小心的使用。后来为了提高程序开发的速度以及程序的健壮性,Java和C#等高级语言引入了GC机制,即程序员不需要再考虑内存的回收等,而是由语言特性提供垃圾回收器来回收内存。但是随之而来的可能是程序运行效率的降低。
GC过程是:先stop the world,扫描所有对象判活,把可回收对象在一段bitmap区中标记下来,接着立即start the world,恢复服务,同时起一个专门gorountine回收内存到空闲list中以备复用,不物理释放。物理释放由专门线程定期来执行。
GC瓶颈在于每次都要扫描所有对象来判活,待收集的对象数目越多,速度越慢。一个经验值是扫描10w个对象需要花费1ms,所以尽量使用对象少的方案,比如我们同时考虑链表、map、slice、数组来进行存储,链表和map每个元素都是一个对象,而slice或数组是一个对象,因此slice或数组有利于GC。
GC性能可能随着版本不断更新会不断优化,这块没仔细调研,团队中有HotSpot开发者,应该会借鉴jvm gc的设计思想,比如分代回收、safepoint等。
初始化阶段直接分配一块大内存区域,大内存被切分成各个大小等级的块,放入不同的空闲list中,对象分配空间时从空闲list中取出大小合适的内存块。内存回收时,会把不用的内存重放回空闲list。空闲内存会按照一定策略合并,以减少碎片。
编译涉及到两个问题:编译速度和依赖管理
目前Golang具有两种编译器,一种是建立在GCC基础上的Gccgo,另外一种是分别针对64位x64和32位x86计算机的一套编译器(6g和8g)。
依赖管理方面,由于golang绝大多数第三方开源库都在github上,在代码的import中加上对应的github路径就可以使用了,库会默认下载到工程的pkg目录下。
另外,编译时会默认检查代码中所有实体的使用情况,凡是没使用到的package或变量,都会编译不通过。这是golang挺严谨的一面。
由于golang诞生在互联网时代,因此它天生具备了去中心化、分布式等特性,具体表现之一就是提供了丰富便捷的网络编程接口,比如socket用net.Dial(基于tcp/udp,封装了传统的connect
、listen
、accept
等接口)、http用http.Get/Post()
、rpc用client.Call(‘class_name.method_name’, args, &reply)
,等等。
在C,C++中,包括其他的一些高级语言是不支持多个函数返回值的。但是这项功能又确实是需要的,所以在C语言中一般通过将返回值定义成一个结构体,或者通过函数的参数引用的形式进行返回。而在Go语言中,作为一种新型的语言,目标定位为强大的语言当然不能放弃对这一需求的满足,所以支持函数多返回值是必须的。
函数定义时可以在入参后面再加(a,b,c)
,表示将有3个返回值a、b、c。这个特性在很多语言都有,比如python。
这个语法糖特性是有现实意义的,比如我们经常会要求接口返回一个三元组(errno,errmsg,data),在大多数只允许一个返回值的语言中,我们只能将三元组放入一个map或数组中返回,接收方还要写代码来检查返回值中包含了三元组,如果允许多返回值,则直接在函数定义层面上就做了强制,使代码更简洁安全。
在Go语言中直接重用了大部份的C模块,这里称为Cgo。Cgo允许开发者混合编写C语言代码,然后Cgo工具可以将这些混合的C代码提取并生成对于C功能的调用包装代码。开发者基本上可以完全忽略这个Go语言和C语言的边界是如何跨越的。
golang可以和C程序交互,但不能和C++交互。可以有两种替代方案:1、先将c++编译成动态库,再由go调用一段c代码,c代码通过dlfcn
库动态调用动态库(记得export LD_LIBRARY_PATH
);2、使用swig。
golang不支持try…catch
这样的结构化的异常解决方式,因为觉得会增加代码量,且会被滥用,不管多小的异常都抛出。golang提倡的异常处理方式是:
error
对象,调用方判断error
对象。panic
(比如除0),使用defer…recover…panic
机制来捕获处理。严重异常一般由golang内部自动抛出,不需要用户主动抛出,避免传统try…catch
写得到处都是的情况。当然,用户也可以使用panic(‘xxxx’)
主动抛出,只是这样就使这一套机制退化成结构化异常机制了。类型推导:类型定义:支持var abc = 10
这样的语法,让golang看上去有点像动态类型语言,但golang实际上是强类型的,前面的定义会被自动推导出是int
类型。
作为强类型语言,隐式的类型转换是不被允许的,记住一条原则:让所有的东西都是显式的。简单来说,Go是一门写起来像动态语言,有着动态语言开发效率的静态语言。
一个类型只要实现了某个interface
的所有方法,即可实现该interface
,无需显式去继承。
Go编程规范推荐每个Interface只提供一到两个的方法。这样使得每个接口的目的非常清晰。另外Go的隐式推导也使得我们组织程序架构的时候更加灵活。在写JAVA/C++程序的时候,我们一开始就需要把父类/子类/接口设计好,因为一旦后面有变更,修改起来会非常痛苦。而Go不一样,当你在实现的过程中发现某些方法可以抽象成接口的时候,你直接定义好这个接口就OK了,其他代码不需要做任何修改,编译器的自动推导会帮你做好一切。
不能循环引用:即如果a.go
中import了b,则b.go
要是import a会报import cycle not allowed
。好处是可以避免一些潜在的编程危险,比如a中的func1()
调用了b中的func2()
,如果func2()
也能调用func1()
,将会导致无限循环调用下去。
defer机制:在Go语言中,提供关键字defer,可以通过该关键字指定需要延迟执行的逻辑体,即在函数体return
前或出现panic
时执行。这种机制非常适合善后逻辑处理,比如可以尽早避免可能出现的资源泄漏问题。
可以说,defer是继goroutine和channel之后的另一个非常重要、实用的语言特性,对defer的引入,在很大程度上可以简化编程,并且在语言描述上显得更为自然,极大的增强了代码的可读性。
“包”的概念:和python一样,把相同功能的代码放到一个目录,称之为包。包可以被其他包引用。main包是用来生成可执行文件,每个程序只有一个main包。包的主要用途是提高代码的可复用性。通过package可以引入其他包。
编程规范:GO语言的编程规范强制集成在语言中,比如明确规定花括号摆放位置,强制要求一行一句,不允许导入没有使用的包,不允许定义没有使用的变量,提供gofmt工具强制格式化代码等等。奇怪的是,这些也引起了很多程序员的不满,有人发表GO语言的XX条罪状,里面就不乏对编程规范的指责。要知道,从工程管理的角度,任何一个开发团队都会对特定语言制定特定的编程规范,特别像Google这样的公司,更是如此。GO的设计者们认为,与其将规范写在文档里,还不如强制集成在语言里,这样更直接,更有利用团队协作和工程管理。
交叉编译:比如说你可以在运行 Linux 系统的计算机上开发运行 Windows 下运行的应用程序。这是第一门完全支持 UTF-8 的编程语言,这不仅体现在它可以处理使用 UTF-8 编码的字符串,就连它的源码文件格式都是使用的 UTF-8 编码。Go 语言做到了真正的国际化!
官网:https://golang.google.cn/
msi
可以直接安装到windows系统,全程下一步完事
brew install go
目录名 | 说明 |
---|---|
api | 每个版本的 api 变更差异 |
bin | go 源码包编译出的编译器(go)、文档工具(godoc)、格式化工具(gofmt) |
doc | 英文版的 Go 文档 |
lib | 引用的一些库文件 |
misc | 杂项用途的文件,例如Android平台的编译、git 的提交钩子等 |
pkg | Windows 平台编译好的中间文件 |
src | 标准库的源码 |
test | 测试用例 |
执行命令:go version
GOROOT
也就是golang的安装目录根路径,配置到环境变量就好了
如果要使用go
命令,需要将%GOROOT%/bin
配置到系统环境变量中
GOPATH 是一个路径,用来存放开发中需要用到的代码包(依赖),注意,这个是一个独立的环境变量,不是配置到PATH里
注意:如果需要在命令行中直接调用GOPATH中install的工具,就需要将$GOPATH/bin
配置到环境变量PATH中
proxy 顾名思义就是代理服务器的意思。Go语言在 1.13 版本之后 GOPROXY 默认值为https://proxy.golang.org
,由于国内的网络有防火墙的存在,这导致有些Go语言的第三方包我们无法直接通过go get
命令获取。GOPROXY 是Go语言官方提供的一种通过中间代理商来为用户提供包下载服务的方式。要使用 GOPROXY 只需要设置环境变量 GOPROXY 即可。
目前公开的代理服务器的地址有:
Windows 下设置 GOPROXY 的命令为:
go env -w GOPROXY=https://goproxy.cn
MacOS 或 Linux 下设置 GOPROXY 的命令为:
export GOPROXY=https://goproxy.cn
执行命令:go env
注意:此项目管理方式为最早的go1.0时所使用,现在不推荐使用,原因是:GOPATH模式下没有版本控制的概念,在执行 go get
的时候,获取的永远是最新的依赖包,下载到GOPATH/src
,如果你有两个工程依赖一个包的v1和v2版本,则会发生冲突,因为 GOPATH 模式下两个工程内依赖的导入路径都是一样的,因此两个工程获取的都是v2版本。
前面搭建Go语言开发环境时添加了环境变量 GOPATH,项目的构建主要是靠它来实现的。就是说,如果想要构建一个项目,就需要将这个项目的目录添加到 GOPATH 中,多个项目之间可以使用;
分隔。
如果不配置 GOPATH,即使处于同一目录,代码之间也无法通过绝对路径相互调用。
--GOPATH
--src //所有源代码都存放到这个文件
--project1 //目录一般为包名称
--xx.go //源码文件
--main.go //源码文件
--project2
--xx.go //源码文件
--bin
--pkg //自动生成,不需要创建
一个Go语言项目的目录一般包含以下三个子目录:
package <包名>
的形式,如 package hello。go get
命令获取到的库源文件下载到 src 目录下对应的文件夹当中。go install
命令安装某个包后的归档文件。归档文件是指那些名称以“.a”结尾的文件。go install
命令完成安装后,保存由 Go 命令源文件生成的可执行文件。在类 Unix 操作系统下,这个可执行文件的名称与命令源文件的文件名相同。而在 Windows 操作系统下,这个可执行文件的名称则是命令源文件的文件名加 .exe 后缀。由于现在的go项目不需要再放在GOPATH下了,目前社区最推荐的项目结构为:https://github.com/golang-standards/project-layout,虽然它并不是官方和社区的规范,但因为组织方式比较合理,被很多 Go
开发人员接受。
web:前端代码存放目录,主要用来存放 Web
静态资源,服务端模板和单页应用(SPAs
)。
cmd:一个项目中可以会有多个组件(模块),对应的命令源码文件存放在这里。
internal:存放私有的库源码文件。你不希望在其他应用和库中被导入,可以将这些代码放在这里,因为在引入其它项目 internal
下的包时,Go
语言会在编译时报错。
pkg:该目录中存放可以被外部应用使用的库源码文件,其他项目可以直接通过 import
导入这里的代码。
vendor:项目依赖,可通过 go mod vendor
创建。需要注意的是,如果是一个 Go
库,不要提交 vendor
依赖包。
third_party:外部帮助工具,分支代码或其他第三方应用(例如 Swagger UI)。比如我们 fork
了一个第三方 go
包,并做了一些小的改动,我们可以放在目录 /third_party/forked
下。
/test
目录的构建方式比较灵活:对于大的项目,有一个数据子目录是有意义的。例如,如果需要 Go 忽略该目录中的内容,可以使用 /test/data
或 /test/testdata
目录。需要注意的是,Go 也会忽略以.
或_
开头的目录或文件。这样在命名测试数据目录方面,可以具有更大的灵活性。Iaas
、PaaS
系统和容器编排部署配置和模板(Docker-Compose
,Kubernetes/Helm
,Mesos
,Terraform
,Bosh
)。在一些项目,特别是用 Kubernetes
部署的项目中,这个目录可能命名为 deploy
。systemd
,upstart
,sysv
)和进程管理配置文件(runit
,supervisord
)。比如 sysemd
的 unit
文件。这类文件,在非容器化部署的项目中会用到。/scripts/make-rules
:用来存放 makefile 文件,实现 /Makefile 文件中的各个功能。Makefile 有很多功能,为了保持它的简洁,我建议你将各个功能的具体实现放在 /scripts/make-rules 文件夹下/scripts/lib
:shell 库,用来存放 shell 脚本。一个大型项目中有很多自动化任务,比如发布、更新文档、生成代码等,所以要写很多 shell 脚本,这些 shell 脚本会有一些通用功能,可以抽象成库,存放在 /scripts/lib 目录下,比如 logging.sh,util.sh 等。/scripts/install
:如果项目支持自动化部署,可以将自动化部署脚本放在此目录下。如果部署脚本简单,也可以直接放在 /scripts 目录下。/build/package
:存放容器(Docker
)、系统(deb
, rpm
, pkg
)的包配置和脚本。/build/ci
:存放 CI
的配置文件和脚本。/build/docker
:存放子项目各个组件的 Dockerfile
文件。/pkg
和 /internal
目录的代码。Git
钩子。比如,我们可以将 commit-msg
存放在该目录。CSS
、JavaScript
等)。Github
页面,则在这里放置项目的网站数据。README
文件一般包含了项目的介绍、功能、快速安装和使用指引、详细的文档链接以及开发指引等。/docs/devel/{en-US,zh-CN}
:存放开发文档、hack 文档等。/docs/guide/{en-US,zh-CN}
: 存放用户手册,安装、quickstart、产品文档等,分为中文文档和英文文档。/docs/images
:存放图片文件。CONTRIBUTING.md
不仅能够规范协同流程,还能降低第三方开发者贡献代码的难度。/api/protobuf-spec
、/api/thrift-spec
、/api/http-spec
、openapi
、swagger
的目录,这些目录包含了当前项目对外提供和依赖的所有 API 文件。Apache 2.0
、MIT
、BSD
、GPL
、Mozilla
、LGPL
。CHANGELOG
目录。不要使用src目录:在默认情况下,Go
语言的项目都会被放置到 $GOPATH/src
目录下(GOPATH模式的时候)。这个目录中存放着所有代码,如果我们在自己的项目中使用 /src
目录,这个包的导入路径中就会出现两个 src
。
对于小型项目,可以考虑先包含 cmd
、pkg
、internal
3 个目录,其他目录后面按需创建。
使用需要安装插件
安装后重启vscode,使用ctrl+shift+p
,搜索go:install/update tools
,全选进行安装
安装完成后,可以看到bin目录下出现这些可执行工具
mkdir helloworld
cd helloworld
go mod init top.ygang/helloworld
//这是一个main包
//这是main包的包注释
package main // 声明 main 包
import (
"fmt" // 导入 fmt 包,打印字符串是需要用到
)
func main() { // 声明 main 主函数
fmt.Println("Hello World!") // 打印 Hello World!
}
Go语言以“包”作为管理单位,每个 Go 源文件必须先声明它所属的包,格式如下:
package name
package 是声明包名的关键字,name 为包的名字
在包声明之后,是 import 语句,用于导入程序中所依赖的包,导入的包名使用双引号""
包围,如果一个import导入多个包,需要用()
包围,并且每个包名占一行,格式如下:
import "name"
import(
"name1"
"name2"
)
注意,导入的包中不能含有代码中没有使用到的包,否则Go编译器会报编译错误,例如 imported and not used: "xxx"
main 函数,它是Go语言程序的入口函数,也即程序启动后运行的第一个函数。main 函数只能声明在 main 包中,不能声明在其他包中,并且,一个 main 包中也必须有且仅有一个 main 函数。
func 函数名 (参数列表) (返回值列表){
函数体
}
_
组成,其中,函数名的第一个字母不能为数字,并且,在同一个包内,函数名称不能重名。func foo(a int, b string)
。注意:Go语言函数的左大括号{
必须和函数名称在同一行,否则会报错。
Go语言是编译型的静态语言(和C语言一样),所以在运行Go语言程序之前,先要将其编译成二进制的可执行文件。
可以通过Go语言提供的go build
或者go run
命令对Go语言程序进行编译:
go build
命令可以将Go语言程序代码编译成二进制的可执行文件,但是需要我们手动运行该二进制文件;go run main.go
命令则更加方便,它会在编译后直接运行Go语言程序,编译过程中会产生一个临时文件,但不会生成可执行文件,这个特点很适合用来调试程序。在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号;
结尾,因为这些工作都将由 Go 编译器自动完成。
如果你打算将多个语句写在同一行,它们则必须使用;
人为区分,但在实际开发中并不鼓励这种做法。
fmt.Println("Hello, World!")
fmt.Println("你好")
注释不会被编译,每一个包应该有相关注释。
单行注释是最常见的注释形式,可以在任何地方使用以//
开头的单行注释;多行注释也叫块注释,均已以/*
开头,并以 */
结尾。
// 单行注释
/*
多行
注释
*/
一般情况下,对于方法、变量、包的注释直接加在对应元素上方就可以。
// this is my package
package mypackage
// this is my int
var myint int
// this is my func
func myfunc(){}
对于包中有很多源代码文件,后续可能会忘记包注释写在哪一个源代码文件中所以一般在对应包的根目录中创建一个文件doc.go
,这个文件只声明package name
,并且添加包注释,而其他源代码文件不再添加包注释。
go doc
是Go自带的命令,可以通过go doc packageName
查看包的说明
godoc
需要额外下载并安装
go get -u golang.org/x/tools/cmd/godoc
go install golang.org/x/tools/cmd/godoc@latest
可以使用godoc -http=:6060
查看GO API文档,如果在项目目录中使用,还包含了项目的API文档
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A-Z和a-z)
数字(0-9)
、下划线_
组成的序列,但是首个字符不能是数字,标识符不可以使用关键字或保留字
Go 代码中会使用到的 25 个关键字或保留字
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
除了以上关键字,Go 语言还有 36 个预定义标识符
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
---|---|---|---|---|---|---|---|---|
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
中文文档:https://studygolang.com/pkgdoc