Go 语言基础
start at 2023/01/15.

参加了第五届字节青训营,所以就做一下 Go 的笔记,按理说我所在的外包项目要求我学 Spring,但是我有点受不了 Java,好像有种天生的抗拒,先学学 Go 吧,后端应该是相通的吧

说实话,我也不知道以后会不会走后端的路,我感觉还是运维比较适合自己,但是先探探路吧,说不准就喜欢上后端了,干几年后端再转运维也不是不行(不要这么自信啊,看看现在的互联网寒冬,显然不行!

但是时间都是自己的,用来打游戏浪费时间也是咎由自取,不如拿来学学东西,要是我高中也有这样的觉悟就好了…

Go,全称 Golang,是由 Google 的程序员设计,是一种静态强类型、编译型、并发型,并具有垃圾回收的功能的高级语言

同时也是一门奇怪的语言,有奇怪的规定,比如说

  1. 不能加分号
  2. 左大括号不能换行
  3. iffor 的条件不能加小括号
  4. }else 必须同行

注意,是不能,不是不用,如果没注意的话会被报错的

前面两条我都能接受,但是后面两条我是强烈不适!

大概是设计 Go 语言的程序员想通过 Go 语言风靡全球来让远古时代就开始的圣战结束吧

同时还内置 gofmt 工具,能自动整理代码多余的空白、变量名称对其、把对其空格转换成 Tab

安装 Go

我只讲在 manjaro 中配置 Go,只需要一行代码

$ sudo pacman -S go

安装完之后,我们还需要做两件事

  1. 创建 go 的工作目录
  2. 设置环境变量

随意选择一个地方创建,我倾向与在 ~/workspace/go/ 这个文件夹,再在里面创建 ~/workspace/go/gobin/ 用于存放二进制文件,~/workspace/go/src/ 用于存放源码和新建项目

然后来配置环境变量,也就是 $PATH 在你的终端初始文件里设置下面几行

其中第二行需要你查看 whereis go 的返回值来确定

export GO111MODULE=on
export GOROOT=/usr/lib/go
export GOPATH=~/workspace/go
export GOBIN=~/workspace/go/gobin
export GOPROXY=https://goproxy.cn
export PATH=$PATH:$GOROOT/bin:$GOBIN

然后在 vim 下 :CocInstall coc-go 等待安装完你就拥有 Go 的 lsp 补全支持了(好耶

顺道提一嘴,coc-go 用的服务端是官方的 gopls,不用的时候记得 kill

Go 的语法

像开头说的,Go 的语法相当的奇怪,反主流

一段 Go 的入门程序会像下面这样

package main

import (
    "fmt"
)

func main() {
    fmt.Print("hello, world")
}

其实我也不是很想在这里啰嗦 Go 的基础语法,就是传统高级语言那套再加上点设计师的恶趣味,你可以通过这个网站来认识 Go 的语法

再提几嘴我注意到的

Go 的函数申明和调用没有强制先后顺序,下面这段程序是可以正常运行的,如果你有一定的 C 语言基础就一定会以为下面这段程序会报错吧,但它能顺利的通过编译并且运行!

package main

import (
    "fmt"
)

func main() {
    fmt.Print(a())
}

func a()int {
    return 6
}

math/rand 随机数需要自己手动添加种子,否则随机出来的都是同一个数字

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().Unix())
    fmt.Print(rand.Intn(233))
}

forrange 的用法,不禁让人想起 for i in range(x):

for i := range arr{
    ...
}

defer 语句用来将函数推迟到外层函数返回后执行,defer 可以执行多条,它是一个栈

package main

import "fmt"

func main() {
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")
}

这段程序会输出是从 9 到 0,而不是从 0 到 9

另外 Go 的指针的用处很局限,只能传实参和节省内存开销这几种用处罢了

协程

协程不等于线程,看上去这两个东西好像是同一个东西,达到的效果也相同,但是由于底层的实现上有区别,协程更加轻量,可以比线程跑的更多,Go 在高并发中使用的就是协程

在 Go 中,这个叫做 Goroutine,使用协程也很简单,只需要在调用函数前面加上 go 就行

for i := 0; i < 5; i++ {
    go fun(i)
}

这样就创建了 5 个协程,他们会像多线程一样分别自己管自己跑,不会在 fun(1) 结束之后才跑 fun(2)

既然协程比线程轻量,那么可以把线程优化掉吗?

并不行。事实上,线程和协程表面上看起来差不多,但是本质是不一样的。通俗地讲,线程使用的是操作系统级别的资源,而协程是调用程序自身所在的线程,也叫用户级别的资源。操作系统的资源是非常宝贵的资源,是有限的,因此能开的线程就相对协程较少

下图就能方便理解这两个概念了

threadGoroutine

信道

熟悉多线程的朋友一定知道既然不是串行,那么一定需要通信,一般的语言都采取 通过内存通信,而 Go 是反过来的,采取 通信来交换内存,这样做同样能减少资源的消耗,所以 Go 语言的设计师相当注重在低配环境下运行程序啊(?

在 Go 语言中,信道(channel,也可以叫通道)就充当了 goroutine 之间连接工具的角色

和 goroutine 一样,信道的创建也是非常简单,创建一个信道只需 make(chan 数据类型) 即可

ch := make(chan int)

信道主要的操作有两种:发送、接收。同时还有一个关闭操作,关闭之后就不能向信道中发送数据,接收方会接收到信道为空为止

// 发送 x 到信道中
ch <- x
// 接收信道中的值
x = <-ch
// 接收信道,但是丢弃结果
<-ch
// 不使用信道了,关闭信道,由发送方 close
close(ch)

一般情况下,信道是没有缓存的,这就导致了如果信道里有东西,那么发送的那一方就无法王信道中发送,处于一个阻塞的状态,整个程序就停在那里,等待信道有空间为止才会继续

像下面的图片那样,供过于求,goroutine2 无法将数据放进去,于是就停在那里了

channel2023116

这非常影响程序的效率,为了防止这种现象的发生,信道也可以设置上缓存,设置缓存的方法是 make(chan int, 容量)

ch := make(chan int, 5)

这样 channel 就有足够的空间让 goroutine 可以先把数据放在 channel 的缓存中,继续手头的工作生产数据,真的是很资本家呢!

channel20231162242

看到这两个 Go 的东西,感觉 Go 的程序会有很多优化空间,比如说要设置多大的缓存不会产生浪费也不会产生阻塞、要跑多少个 goroutine 才能让效率最大,这对测试来说无疑是一项很大工作量,不过大多数程序员是不会那么追求效率的吧(

关于依赖

依赖就是指用户在开发过程中使用的非本人编写的代码函数库,类似 python 中的 import xxx ,不过由于 go 是一门编译型语言,所以使用依赖都是需要源代码然后编译进程序的,所以对于一门编译型语言来说,如何管理依赖是一个重要的问题,对依赖的管理主要就是同一个依赖的不同版本问题,go 的依赖管理方案有两次迭代

第一次迭代是 GOPATH->Go Vendor,利用不同项目使用依赖的副本放在项目文件夹下,解决了 GOPATH 所有项目都使用 src 目录下同一个版本的依赖导致的更新依赖后出现有些项目不兼容的问题

第二次迭代是 Go Vendor->Go ModuleGo Vender 的方案还是会有问题,当一个项目使用其他多个项目的时候,后者之间会存在依赖冲突,如果只使用当前项目文件夹中的依赖副本,还是会有问题

Go Module 就解决了这个问题,Go Module 的方案是通过 go.mod 文件来管理依赖包的版本,通过 go get 或者 go mod 指令来管理依赖包,go.mod 文件中包含了很多重要信息,require 后面跟的就是项目需要的依赖和对应的版本号了,其中有注释indirect 的就是指间接依赖,就是指依赖的依赖,+incompatible 是指可能由于版本问题不兼容的依赖

对于版本号也是有考究的,版本号的第一个数字是大版本,大版本之间可能会有些函数做出删减,会出现不兼容的问题,第二个数字是小版本,小版本之间是向下兼容的,第三个数字是补丁号,当出现依赖冲突的时候,go 会贪心地选择可以运行的最低兼容版本,比如说你要用 v1.5v1.4,因为 v1.5 向下兼容,所以 go 就会采用 v1.5 版本

依赖下载通常会从 github 上下载,但是众所周知,国内的网速非常感人,所以我们通常需要配置 Proxy 来下载,一般都会在配置文件里

export GOPROXY=https://goproxy.cn

你也可以多来几个 Proxy

export GOPROXY="https://proxy1.cn,https://proxy2.cn,direct"

顺序是从前往后检索的,好像也可以不配置环境变量来配置 GOPROXY

$ go env -w GOPROXY=https://goproxy.cn,direct

使用 Go Modules 是需要告诉 go 的,GO111MODULE 这个环境变量可以设置成 off on auto ,开启之后 go 会忽略 GOPATHGo vendor 的风格,只根据 go.mod 下载依赖

go get

$ go get example.org/pkg

可以下载依赖,这样下载是默认最新版本的,也可以下载指定版本

$ go get example.org/pkg @v1.1.2

go mod

$ go mod init # 创建初始 go.mod 文件
$ go mod download # 下载依赖到本地缓存
$ go mod tidy # 优化依赖

虽然还没正式开始大量使用依赖,但是总体感觉比 java 好太多,我就是由于不理解 java 的编译过程,再加上 cli 上开发 java 的例子太少,好像是可以用 maven 来帮助构建项目目录,但我还是比较抗拒这些懒人包工具的,网上找 vim 如何写 java,大多数文章就写 java 的 lsp,主要还是会把 vim 当成 ide 使用,我就受不了那样,所以我就没学进 spring,虽然装了 IDEA,但是还是等 go 学成之后再去学 spring 框架吧,感觉后端的工作其实挺傻的(?

2023/01/18
> CLICK TO back <