Go语言上手 | 青训营笔记

184 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记。

Go语言上手-基础语言

标准库

Golang的os库提供了丰富且强大的功能,像我们所熟知的一些函数os.Open,同时标准库还定义了FileProcess等类型,实现了对其操作的许多方法,极大的便利了用户对文件,进程等操作。

package main

import (
	"fmt"
	"os" // https://pkg.go.dev/os
	"os/exec"
)

func main() {
	fmt.Println(os.Args) // 返回执行 go run 参数 时其中包含的参数

	fmt.Println(os.Setenv("Hello", "/tmp")) // 设置临时变量
	fmt.Println(os.Getenv("Hello"))

	buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
	if err != nil {
		panic(err)
	}
	fmt.Println(string(buf))
}

tools

方便Golang做web开发的一些网站:

Go语言上手-工程实践

从并发视角理解Golang

一个经典面试问题:并发和并行是什么?

df2c4def-f837-49cd-abd7-84fbe3241622.png

并发:多线程程序在单核 CPU 上运行,本质是通过划分时间切片,榨干 CPU 资源。

并发分为广义和狭义,上面是狭义的定义,广义上,可以说我完成了一个高并发项目。

ab231fab-c4f1-478b-8e4e-dbebbc21993a.png

并行:多线程程序在多个核的 CPU 上运行,本质是能够同一时间能够处理多个任务。

concurrency is not parallelism演讲:go.dev/blog/waza-t…

fd324ad6-663f-464d-a561-4b9fbabcc213.png

线程:内核态,轻量级线程,栈MB级别。

协程:用户态,线程跑多个协程,栈KB级别。

线程是CPU调度的基本单元,协程是CPU调度的基本单元。

A goroutine is a lightweight thread managed by the Go runtime.

goroutine vs coroutine(协程)

两者存在相似的地方,显然“goroutine”这个名字源于这种相似性。

协程和 goroutine 的区别在于:

  • goroutines 意味着并行性,可以并行执行;协程一般不会,通常顺序执行
  • goroutines 通过channel进行通信;协程通过 yield 和 resume 操作进行通信

coroutine 的运行机制属于协作式任务处理,早期的操作系统要求每一个应用必须遵守操作系统的任务处理规则,应用程序在不需要使用 CPU 时,会主动交出 CPU 使用权。如果开发者无意间或者故意让应用程序长时间占用 CPU,操作系统也无能为力,表现出来的效果就是计算机很容易失去响应或者死机。

goroutine 属于抢占式任务处理,已经和现有的多线程和多进程任务处理非常类似。应用程序对 CPU 的控制最终还需要由操作系统来管理,操作系统如果发现一个应用程序长时间大量地占用 CPU,那么用户有权终止这个任务。

对于大部分人来说,goroutine就是Go中的协程。本质上,goroutine是对线程的复用。

af8abbb6-97f3-4963-91e7-c6c25c9d3cd6.png

提倡通过通信共享内存而不是通过共享内存而实现通信。

Go语言更多问题解答:go.dev/doc/faq

Go的依赖管理

Go 依赖管理演进

GOPATH -> Go Vendor -> Go Module

GOPATH:

  • 环境变量 $GOPATH
  • 项目代码直接依赖 src 下面的代码
  • go get 下载最新版本的包到 src 目录下

弊端:两个项目依赖某一 package 的不同版本时,无法做到 package 多版本并发控制

Go Vendor:

  • 项目下增加 vendor 文件,所有依赖包副本形式放在 $ProjectRoot/vendor
  • 依赖寻址方式:vendor => GOPATH

解决了GOPATH的弊端

弊端:一个项目依赖两个 package 同时两个包又依赖某一 package 的不同版本时,无法控制依赖版本,更新项目可能出现依赖冲突,导致编译错误

Go Module:

  • 通过 go.mod 文件管理依赖包版本
  • 通过 go get/ go mod 指令工具管理依赖包

依赖管理三要素

  • 配置文件,描述依赖 go.mod
  • 中心仓库管理依赖库 Proxy
  • 本地工具 go get/mod

依赖配置

go.mod

module example/project/app // 依赖管理基本单元

go 1.16 // 原生库

// 单元依赖
require (
	example/lib1 v1.0.2
	example/lib2 v1.0.0 // indirect
	example/lib3 v0.1.0-20190725025543-5a5fe074e612
	example/lib4 v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	example/lib5/v3 v3.0.2
	example/lib6 v3.2.0+incompatible
) 

依赖标识:[Module Path] [Version/Pseudo-version]

version

语义化版本

${MAJOR}.${MINOR}.${PATCH} v1.3.0 v2.3.0

基于 commit 伪版本

vX.0.0-yyyymmddhhmmss-abcdefgh1234 v0.0.0-20220401081311-c38fb59326b7 v1.0.0-20201130134442-10cb98267c6c

indirect

A->B->C = A->B 直接依赖 / A->C 间接依赖

incompatible

  • 主版本2+模块会在模块路径增加 /vN
  • 对于没有 go.mod 文件并且主版本2+的依赖,会+incompatible

依赖分发

880c356b-4bac-4737-b44b-893981d801bb.png

Go Proxy 是一个服务站点,它会缓存源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用,从而实现了供“immutability”和“available”的依赖分发;使用 Go Proxy 之后,构建时会直接从 Go Proxy 站点拉取依赖。

GOPROXY="proxy1.cn, proxy2.cn, direct"

服务站点URL列表,"direct"表示源站。 Proxy 1 -> Proxy 2 -> Direct

**工具 **

go get example.org/pkg

@update 默认

@none 删除依赖

@v1.1.2 tag版本,语义版本

@23dfdd5 特定的commit

@master 分支的最新commit

go mod

init 初始化,创建 go.mod 文件

download 下载模块到本地缓存

tidy 增加需要的依赖,删除不需要的依赖

测试

单元测试

规则:

  • 所有测试文件以 _test.go 结尾
  • func TestXxx(*testing.T)
  • 初始化逻辑放到TestMain中

f89ba6dc-629b-43c0-a7ba-b676e2d15e9a.png 64d3c842-ddda-41d8-892c-69b842881196.png

d8542f98-62ee-41dd-9c19-27e874be83c8.png

运行:go test [flags] [packages]

assert包:使用 assert 包,方便编写测试文件

tips:

  • 一般覆盖率 50%~60%,较高覆盖率80%+
  • 测试分支相互独立、全面覆盖
  • 测试单元粒度足够小,函数单一职责

外部依赖->稳定&幂等

快速Mock函数:为一个函数打桩;为一个方法打桩

monkey:github.com/bouk/monkey

基准测试

使用rand随机选择模拟场景,进阶可使用fastrand提升测试效率。