Go语言原理与实践

​点击阅读更多查看文章内容

什么是Go语言

  1. 高性能、高并发

    • 与C++、Java相近的性能

    • 标准库内置高并发的支持

  2. 语法简单、学习曲线平缓

  3. 丰富的标准库

  4. 完善的工具链

    • 编译、错误检查、包管理、代码提示
    • 单元测试框架
  5. 静态链接

    • 只需要拷贝编译后的可执行文件就可以部署运行,在容器中运行可以使镜像体积非常小
    • C++需要附加.so文件才能运行、Java需要附加JRE
  6. 快速编译

  7. 跨平台

    • Windows、Linux、MacOS、Android、IOS、路由器、树莓派
  8. 垃圾回收

字节为什么使用GO?

image-20250122152652613

学习路线

image-20250122215518133

image-20250122215543046

image-20250122215556627

Go语言进阶与依赖管理

并发编程

并发:多线程程序在一个核的CPU上运行,通过时间片切换交替执行多个程序

image-20250122220119800

并行:利用多核实现多个线程的同时运行

image-20250122220124552

协程 Goroutine

协程开销很小,是Go语言适合高并发场景的原因

image-20250122220355159

CSP(Communicating Sequential Processes)

协程间通信

image-20250122221117328

Channel

通过通信共享内存

image-20250122222345782 image-20250122222355807

通过共享内存实现通信

对共享内存的访问需要加锁

image-20250122222746635

WaitGroup

之前的例子是通过等待1s来等待协程执行完毕,这里可以通过WaitGroup使用计数器优雅的等待协程执行完毕

image-20250122222947065 image-20250122223024013

依赖管理

Go依赖管理演进

image-20250122223153261

GOPATH

所有项目共享同一个GOPATH

image-20250122223320351

弊端:

image-20250122224119733

Go Vendor

image-20250122224238019

弊端:

image-20250122224326252

Go Module

image-20250122224520309 image-20250122224530555

go.mod

image-20250122224626369

version

major是大版本,不同major可能是不兼容的,minor是大版本下的下版本,同一大版本下是兼容的,patch是对bug的修复

image-20250122224943752

版本前缀-时间戳-哈希

image-20250122224952036

indirect

image-20250122225007295

incompatible

image-20250122225254839

如果两个依赖库需要不同版本的同一依赖库,而这些版本无法同时兼容,Go Vendor 无法解决此问题。

Go Modules 会自动选择一个最小兼容版本,避免冲突。

image-20250122225526714

依赖分发

不会直接去Github等依赖的源仓库拉取

image-20250122225659055

通过Go Proxy缓存源站中的软件内容,缓存的软件版本不会改变,并且在源站删除之后依然可用

image-20250122225724272

GOPROXY

image-20250122225848285

工具 - go get

image-20250122225936094

工具 - go mod

image-20250122225957337

测试

image-20250122230535255
  • 回归测试:一般是QA同学手动通过终端回归些固定的主流程场景
  • 集成测试:对系统功能维度做测试验证
  • 单元测试:测试开发阶段,开发者对单独的函数、模块做功能验证

层级从上至下,测试成本逐渐减低,而测试覆盖率确逐步上升,所以单元测试的覆盖率定程度上决定这代码的质量。

单元测试

image-20250122230651301

规则:

image-20250122230749558

覆盖率:

image-20250122232017474 image-20250122232127066 image-20250122232139962

依赖:单元测试可能需要依赖外部的文件数据库等,单元测试要求外部依赖是幂等且稳定的

  • 幂等:重复运行case的结果是相同的
  • 稳定:单元测试是隔离的,能在任何时间,任何环境,运行测试。
image-20250122232354740

要实现稳定和幂等就需要Mock函数

例子:文件处理,将第一行字符串中的11替换成00,需要依赖本地文件,如果文件修改测试就会fail

image-20250122232646431

使用开源mock测试库monkey,对method进行mock

Monckey Patch 的作用域在 Runtime,在运行时通过Go的unsafe包,能够将内存中函数的地址替换为运行时函数的地址,将target的方法实现跳转到replacement

image-20250122232713722 image-20250122233212370

基准测试

image-20250122233410720 image-20250122233422136 image-20250122233759927

Benchmark开头,入参为testing.B,用b中的N值反复递增循环测试

(对一个测试用例的默认测试时间是1秒,当测试用例函数返回时还不到1秒,那么testing.B中的N值将按1、2、5、10、 20、 ….递增,并以递增后的值重新进行用例函数测试。)

Resttimer重置计时器,我们再reset之前做了init或其他的准备操作,这些操作不应该作为基准测试的范围; runparaller是多协程并发测试;执行2个基准测试,发现代码在并发情况下存在劣化,主要原因是rand为了保证全局的随机性和并发安全,持有了一把全局锁。

使用fastrand优化

image-20250122233948621

高质量编程简介及编码规范

编码规范

代码格式

image-20250123102742429

注释

image-20250123102814012

Open()的注释解释了函数的作用,IsTableFull()的注释实际上没有提供一些额外的信息

image-20250123102827129

第二个for循环的注释没有什么作用

image-20250123103044036 image-20250123103137624 image-20250123103149807 image-20250123103325507 image-20250123103332349 image-20250123103346998 image-20250123103354175

命名规范

image-20250123103515372 image-20250123103530943 image-20250123103556044 image-20250123103741298 image-20250123103833562 image-20250123103848758

控制流程

image-20250123103931638 image-20250123103954166 image-20250123104115753 image-20250123104145030

错误和异常处理

image-20250123104227159 image-20250123104317872 image-20250123104409602 image-20250123104423988 image-20250123104523677 image-20250123104619633 image-20250123104641510 image-20250123104648252

性能优化

性能优化建议

benchmark

函数命名必须以Benchmark开头,后面跟的单词必须以大写字母开头

image-20250123110855049 image-20250123111354113

Slice

预分配内存

data := make([]int, 0, size):长度为0,容量为size

data := make([]int, size):长度,容量都为size

image-20250123112206318 image-20250123112420595

大内存未释放

1
2
3
4
5
6
func main() {
data := make([]int, 100)
data2 := data[:2]
fmt.Println(cap(data2))
}
// 100
1
2
3
4
5
6
7
8
func main() {
data := make([]int, 100)
// 必须先为data2分配内存才能copy,否则会panic
data2 := make([]int, 2)
copy(data2, data[:2])
fmt.Println(cap(data2))
// 2
}
image-20250123112634329

Map

预分配内存

image-20250123113331272 image-20250123113338257

字符串处理

使用strings.Builder

image-20250123113443683 image-20250123113914497 image-20250123113935731 image-20250123113954450 image-20250123114807705

空结构体

image-20250123114952316 image-20250123115001008

atomic包

image-20250123115028623 image-20250123115040568

小结

image-20250123115049414

性能优化分析工具

image-20250123125459514

性能分析工具pprof

image-20250123125547173 image-20250123125609275 image-20250123231636479

启动main函数,访问 http://localhost:6060/debug/pprof/ 在浏览器查看指标

image-20250123232255802

采集10s的CPU数据 go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"

image-20250123232752702

输入top查看占用资源最多的函数

image-20250123232836032 image-20250123232854933

Eat函数占用资源最多,使用list Eat查看函数

image-20250123233112882 image-20250123233204932 image-20250123234000475
作者

ShiHaonan

发布于

2025-02-13

更新于

2025-03-13

许可协议

评论