Go 语言基础系列(一)基础特性

前言

本文为笔者所著 Go 语言基础系列之一,本文笔者将概述 Go 语言最为核心的特性,这些特性也是最为打动笔者的地方;

本文为作者原创作品,转载请注明出处;

编译

程序编译总朗阔为 4 个部分,如下,$$\xrightarrow{Golang源码}\text{语法和词法分析}\xrightarrow{AST树} \text{类型检查}\xrightarrow{合法的AST树} \text{生成中间代码}\xrightarrow{中间代码} \text{生成机器代码}$$

过程中最为出彩的就是通过中间代码生成机器代码的这个步骤;我们知道,Java 语言编译得到中间代码.class文件后,直接交给 Java 虚拟机来处理,由 Java 虚拟机来保证 Java 语言的跨平台执行;但是 Golang 更进一步,直接生成目标机器的机器代码,也就是目标机器的可执行程序,比如 windows 中的 .exe 文件,Linux 中的 .sh 文件等等,这样使得它非常轻量级的实现了语言的跨平台的特性;

不过要能够直接生成目标机器的机器代码也绝非易事,因为不同架构的 CPU 对应着不同的机器指令集,这就使得 Golang 在生成目标机器代码的时候,必须根据不同 CPU 的指令架构生成对应的机器代码;因此,在编译的时候,通常我们需要指定目标机器 CPU 的指令架构,比如,我们需要编译生成 darwin 操作系统并基于 AMD 指令架构的机器代码,

1
$ GOARCH=amd64 GOOS=darwin make sample.go

在编译 sample.go 的时候,通过GOARCH指定 CPU 的指令架构,通过GOOS指定操作系统或者运行环境;这样,我们就将源码sample.go生成了目标系统的机器代码;

常见的 CPU 指令架构有,amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm 等;

协程

最为打动我的地方就是协程 Goroutine了,为了避免像传统编程语言直接使用内核线程来实现并发,导致高并发场景下,系统资源的极大浪费的问题(在高并发下因内核线程的频繁切换造成 CPU 上下文的频繁切换,进而导致系统资源的极大浪费),Golang 居然自己创建了一套在用户层的虚拟线程 Goroutine,它模拟了内核线程的机制,每个Goroutine拥有自己的栈,堆,来保存自己的临时变量和对象,并且所有的线程切换(挂起、唤醒、执行等)都是在用户层的 Go runtime 中实现的,这样使得 Go 语言在高并发场景下的执行效率非常的高,因为它再也不会直接导致 CPU 进行上下文切换了;
Golang 协程概念图 如图,假设,当前我们有 4 个 Goroutine 虚拟线程,单核 CPU;Go runtime 负责调度,将不同的 Goroutine 线程分配到不同的内核线程中去执行,如图,Goroutine A 和 B 分配给了内核线程 1,C 和 D 分配给了内核线程 2;

特点,

  • Goroutine 线程之间的切换是在用户层完成的,内核线程和 CPU 无感知,因此 Golang 的并发性非常的好;
  • 如果某个 Goroutine 线程的不当操作导致了内核线程被挂起了,Go runtime 可以及时的将其它的 Goroutine 线程调度到另外一个内核线程中去执行,保证 Goroutine 一直在执行中;

综上,Golang 就是一门为并发而生的语言;

垃圾回收器

Golang 的垃圾回收主要使用到了三色标记法,写屏障等技术来清理内存;

总结

正是因为 Golang 所独有的跨平台特性以及其强大的协程,吸引了我,决定开启 Golang 之旅;