go语言诞生于2007年,由Google首席软件工程师Rob Pike与Robert Griesemer和Ken Thompson两位大师创造,目的是用来取代C++语言,来解觉google工作者在使用C++语言带来的问题,2009年11月首次向公众发布并开源,自开源后在google内部和公众,都得到了广发的应用,国内大厂字节跳动、腾讯、阿里巴巴等纷纷转型,数以百万计的开发者开始投入Go语言的怀抱。
Go 语言具有很强的表达能力,简洁、清晰且高效。由于其独特的并发机制,用它编写的程序能够非常有效地利用多核与联网的计算机,其新颖的类型系统则使程序结构变得灵活而模块化。Go代码编译成机器码不仅非常迅速,还具有方便的垃圾收集机制和强大的运行时反射机制。它是一个快速的、静态类型的编译型语言,感觉却像动态类型的解释型语言。
随着编程人员对编程语言在易用、易学、低代码等方面的要求越来越多,加之C/C++近年来相关的人才越来越难招,学习和人力成本越来越高,python、scala、go等编程语言,愈来愈流行。其中go语言由于其独特的优势,特别在并发性能和易用性上都表现较好,在某些场景下得到了广泛额应用,结合网上资料及总结,下面分析下go语言的优势和劣势
主要优势体现在如下方面:
简单易学:Go语言语法简单,包含了类C语法,很容易上手,开发速度快
媲美C的高性能:可直接编译成机器码,不依赖其他库,相比于java及python,具有更高的性能,夸张点说可媲美C
开发效率高:编译快、开发快、运行高效,相较于 Java 和 C++的编译速度,Go 的快速编译是主要的效率优势。Go拥有接近C的运行效率和接近PHP的开发效率。
自由灵活:Go语言支持当前所有的编程范式,包括过程式编程、面向对象编程、面向接口编程、函数式编程,开发者可自由组合和扩展
高并发性:通过goroutines 和通道,天生支持并发变成,goroutine通过协程实现并发,占用的资源低,几十个goroutine可能体现在底层就是五六个线程,Go语言内部实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),可同时运行成千上万个并发任务,goroutine比thread更易用、更高效、更轻便。
生态强大:背靠google,go语言有着强大的生态系统,有很多开源的框架可选用,有面向 Redis、RabbitMQ、PostgreSQL、Template parsing、Task scheduling、Expression parsing 和 RocksDB 等的稳定强大的标准库。相比于 Rust、Elixir 这样的语言有很大的优势,但是又略逊于 Java、Python语言,但是其高性能和高效率,可弥补。
部署简单:直接生成机器码,可二进制文件直接部署,因为部署太方便了,这一点是很多人选择Go的最大理由
易于组建团队:任何的 Python、Elixir、C++、Scala和 Java 开发者皆可在一月内组建成一个高效的Go团队。
当然,go语言也有很多的不足之处,以下列出一些主要的不足:
框架不够丰富: Go 语言没有一个主要的成熟框架,开发相比于python等还是没那么便捷
软件包管理不完善:包不支持版本,需要自己控制版本信息
缺乏有经验的go工作者:会用go很多,深入研究的很少,尽管有些程序员带着java和c++的丰富分布式编程经验来到go,但其实好多人并不知道如何针对go的特性来设计和使用API,这造成非常多的go项目是不一定能经受住长期维护考验的
Go语言在云计算、边缘计算、大数据、微服务、物联网、高并发领域应用得越来越广泛,越来越多的知名公司正在把Go作为开发新项目的首选语言,golanguage主要在如下场景中有着广泛的应用:
go大部分场景能够替代c/c++,但是在某些场景c/c++还是不能被替代,例如实时操作系统和设备驱动程序的开发。
由于我们公司准备使用go语言作为嵌入式开发语言,下面重点介绍下go语言相比于c/C++在嵌入式开发中的优劣势。
嵌入式变成一般对开发语言有如下要求:
编译后的程序尽可能小,由于嵌入式硬件一般硬件资源有线
能够兼容嵌入式系统,如嵌入式linux系统
具备可移植性,嵌入式软件一般在windows下开发,需要具备跨平台调试和运行的能力
具备网络编程的能力,嵌入式需要支持TCP/IP协议,开发语言需要有相应的支持
尽可能少的外部库依赖,因为外部依赖,可能引入兼容性的问题
兼容C语言的接口,因为嵌入式驱动层几乎还是c语言
从嵌入式的基本要求,语言的开发难度等方面,对c/c++/go进行对比,如下:
语言 | c | c++ | go |
---|---|---|---|
设备存储要求 | 最低 | 低(比c多出几兆) | 低(比c++多出几兆,在一个量级) |
跨平台能力 | 支持 | 支持 | 支持 |
缓冲不足/溢出保护 | 不支持 | 轻度支持 | 支持 |
c接口兼容 | 完全兼容 | 完全兼容 | 支持导入c接口 |
自动内存管理 | 不支持 | 不强制支持(需要自行实现) | 支持 |
标准数据容器 | 不支持 | 支持 | 支持 |
HTTP库 | 外部依赖或者开发实现 | 外部依赖或者开发实现 | 内置 |
JSON | 外部依赖或者开发实现 | 外部依赖或者开发实现 | 内置 |
SSL/TLS | 外部依赖或者开发实现 | 外部依赖或者开发实现 | 内置(可选软件包) |
共享依赖关系 | 是 | 是 | 否 |
运行速度 | 最高 | 较高 | 高 |
编译速度 | 较慢 | 最慢 | 极快 |
从上述表中可以看出,由于Go应用程序可以静态编译为单个二进制文件,因此它并不需要任何额外的虚拟环境(比如Java除了二进制文件之外还需要虚拟机),Go代码在设备上运行也不需要依赖其他项,另外Go在代码编译后生成的可执行文件大小也是可以与C,C++和C++ / Qt相媲美,且go语言有极其丰富的标准库,非常易于开发,虽然在代码编译后生成的程序体积上,略大于c/c++,但是也在能够接受的范围内,因此从编译速度、易用性、保护机制、标准库数量等方面,如果嵌入式不是特别极端的场景下(如追求极致速度及资源使用)的情况下,go语言在嵌入式中可以替代c/c++,当然在驱动层及操作系统层,c还是最好的开发语言。
Go语言需要编译成二进制机器码,包含二进制机器码的文件才能在目标机器上运行,和c/c++类似(预处理/编译/汇编/链接),Go 的编译器在逻辑上分为如下四个阶段:词法与语法分析、类型检查和 AST 转换、通用 SSA 生成和机器代码生成。
编译过程是从解析代码的源文件开始的,词法分析的作用是解析源代码文件,它将文件中的字符串序列转换成 Token 序列,方便后面的处理和解析,由词法分析器(lexer)对源代码文件进行解析。
语法分析阶段,由语法分析器按照顺序解析Token序列,根据编程语言定义好的文法(grammer)对Token进行自下而上或自上而下的归约,转换成有意义的结构体,即抽象语法树。每个 Go 源代码文件最终都会被解析成一个独立的抽象语法树(AST)。有关抽象语法树概念了解,可参照:
https://blog.csdn.net/weixin_52690231/article/details/124691503
每一个 Go 的源代码文件最终会被归纳成一个 SourceFile 结构:
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" }
Token 到上述抽象语法树(AST)的转换过程会用到语法解析器,每一个 AST 都对应着一个单独的 Go 语言文件,这个抽象语法树中包括当前文件属于的包名、定义的常量、结构体和函数等。语法解析的过程中发生的任何语法错误都会被语法解析器发现并将消息打印到标准输出上,整个编译过程也会随着错误的出现而被中止
类型检查会遍历AST中的节点,检验每个节点的类型,找出其中存在的语法错误。此过程中也可能改写AST,包括去除一些不会被执行的代码,优化代码以提高执行效率,而且会修改make、new等关键字对应节点的操作类型。
在类型检查阶段,会根据创建的类型将make替换成特定的函数,后面生成中间代码的阶段就不会再处理OMAKE类型的节点了。比如如果make的第一个类型参数是切片,那么在对参数数量和合法性进行校验之后,还会将当前节点的操作Op改成OMAKESLICE,方便后面编译阶段的处理。
中间代码的生成过程是从 AST 抽象语法树到 SSA 中间代码的转换过程,在这期间会对语法树中的关键字再进行改写,改写后的语法树会经过多轮处理转变成最后的 SSA 中间代码,相关代码中包括了大量 switch 语句、复杂的函数和调用栈,阅读和分析起来也非常困难。
中间代码是编译器或者虚拟机使用的语言,它可以来帮助我们分析计算机程序。在编译过程中,编译器会在将源代码转换到机器码的过程中,先把源代码转换成一种中间的表示形式,即中间代码
由于 Go 语言编译器的中间代码使用了 SSA 的特性,在这一阶段能够分析出代码中的无用变量和片段并对代码进行优化,生成中间代码包括初始化SSA生成的配置和函数编译两部分,详细参照:
https://zhuanlan.zhihu.com/p/454419598
Go 语言编译的最后一个阶段是根据 SSA 中间代码生成机器码,这里谈的机器码是在目标 CPU 架构上能够运行的二进制代码。
机器码的生成过程其实是对 SSA 中间代码的降级(lower)过程,在 SSA 中间代码降级的过程中,编译器将一些值重写成了目标 CPU 架构的特定值,降级的过程处理了所有机器特定的重写规则并对代码进行了一定程度的优化;在 SSA 中间代码生成阶段的最后,Go 函数体的代码会被转换成cmd/compile/internal/obj.Prog结构,机器码的生成在 Go 的编译器中主要由两部分协同工作,其中一部分是负责 SSA 中间代码降级和根据目标架构进行特定处理的 cmd/compile/internal/ssa 包,另一部分是负责生成机器码的汇编器 cmd/internal/obj包,详细请参照:
https://zhuanlan.zhihu.com/p/454419598
这里顺便介绍下go语言在windows下的安装及部署过程,后续文章会针对go的IDE做详细讲解。
go1.18.3.windows-amd64.msi
双击下载文件安装即可,可自定义安装路径
最新版本安装包,安装完成之后会自动配置好环境变量,这一步可以略过,可以直接通过在cmd命令提示行中输入:
go version
如果弹出如下版本信息,则说明环境变量已经配置好,环境安装成功。
创建工作目录,创建go文件(hello.go),编辑hello.go,输入如下代码:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
编写好的代码可以直接通过go run hello.go命令执行,并得到执行结果,在代码所在的目录shift+右键,在此处打开powershell,然后如下操作:
通过go run可以直接运行hello.go,但是在无go环境的情况,不能单独运行,需要编译成二进制文件,才能单独运行,通过如下命令进行编译:
go build hello.go
编译后生成hello.exe,此二进制文件 可以在无go环境下直接运行。
通过文本方式编程开发,很不方便,后续介绍VSCODE中配置go集成开发环境。