Go - Type Parameter
不支持
Feature
Desc
泛型特化
编写一个泛型函数针对某个具体类型的特殊版本
元编程
编写在编译时执行的代码来生成在运行时执行的代码
操作符方法
不能将操作符视为方法并自定义其实现
变长类型参数
容器重复代码123456789101112131415161718192021222324252627func maxInt(sl []int) int { if len(sl) == 0 { panic("empty slice") } max := sl[0] for _, v := range sl[1:] { if v > max { max = v } } return max}func maxString(sl []string) string { if len(sl) == 0 { panic("empty s ...
Go - GC
逃逸分析
在传统的不带 GC 的编程语言中,需要关注对象的分配位置,是分配在堆上还是栈上
Go 集成了逃逸分析功能来自动判断对象是应该分配在堆上还是栈上
只有在代码优化时,才需要研究具体的逃逸分析规则
escape.go123456package mainfunc main() { var m = make([]int, 10240) println(m[0])}
1234$ go build -gcflags='-m' escape.go# command-line-arguments./escape.go:3:6: can inline main./escape.go:4:14: make([]int, 10240) escapes to heap
较大的对象会被放在堆上
如果对象分配在栈上,其管理成本比较低,只需要挪动栈顶寄存器就可以实现对象的分配和释放
如果对象分配在堆上,需要经过层层的内存申请过程
逃逸分析和垃圾回收结合,可以极大地降低开发者的心智负担,无需再担心内存的分配和释放
抽象成本
Rust - 零成本抽象
一切 ...
Go - Module Maintainer
仓库布局单模块
首选:一个 repo 管理一个 module,一般情况下,module path 与仓库地址保持一致
go.mod123module github.com/zhongmingmao/srsmgo 1.18
如果对 repo 打 tag,该 tag 会成为 module 的版本号,对 repo 的版本管理即对 module 的版本管理
12345678$ tree.├── LICENSE├── go.mod├── pkg1│ └── pkg1.go└── pkg2 └── pkg2.go
该 module 对应的包导入路径为
github.com/zhongmingmao/srsm/pkg1 和 github.com/zhongmingmao/srsm/pkg2
如果 module 演进到了 2.x 版本,则包导入路径变更为 github.com/zhongmingmao/srsm/v2/pkg1
多模块1234567891011$ tree.├── LICENSE├── module1│ ├── go.mod│ └── pkg1│ └── p ...
Go - Generics
泛型概念
将算法与类型解耦,实现算法更广泛的复用
实现方向
拖慢程序员
不实现泛型,不会引入复杂性
但需要程序员花费精力重复实现同逻辑但不同类型的函数或者方法
拖慢编译器 – C++/Go
类似 C++ 的泛型实现方案,通过增加编译器的负担为每个类型实例生成一份单独的泛型函数的实现
产生大量的代码,且大部分是多余的
拖慢执行性能 – Java
类似 Java 的泛型实现方案,即伪泛型,通过隐式的装箱和拆箱操作消除类型的差异
虽然节省了空间,但代码执行效率低
泛型设计类型系统
静态强类型:C++/Java/Go;动态强类型:Python;动态弱类型:JavaScript
Go 为强类型语言,而 JavaScript 为弱类型语言,Go 的类型强度高于 JavaScript
Go 和 Python 都有较高的类型强度,但类型检查的时机不同
Go 是在编译期,而 Python 则在运行期
如果类型检查是发生在运行期,则为动态类型语言
动态类型不仅仅表现在变量的类型可以更改,在 OOP 的编程语言中,类的定义也可以动态修改
动态类型的优点
动态类型有更好的灵活 ...
Go - Private Module
导入本地 Module
借助 go.mod 的 replace 指示符
123require github.com/user/b v1.0.0replace github.com/user/b v1.0.0 => 本地源码路径
优化方案:Go workspace
Private Module公网
对 private module 的拉取,不会走 GOPROXY 代理服务,也不会去 GOSUMDB 服务器做 Go 包的 hash 值校验
内网
更主流
方案 1
in-hourse goproxy 类似于 nexus
方案 2
推荐
Go 命令默认会对所有通过 goproxy 拉取到的 Go Module,进行 sum 校验(默认到 sum.golang.org)
为了跳过 sum 验证,需要将 private module 填到 GONOSUMDB 中
实践GOPROXY
编译
123456789$ mkdir goproxy$ cd goproxy/$ git clone https://github.com/goproxyio/goproxy$ cd goproxy/ ...
Go - 1.17
语法特性切片 -> 数组指针
数组切片化,转换后,数组将成为切片的底层数组
1234a := [3]int{11, 12, 13}sl := a[:]sl[1] += 10fmt.Printf("%T, %v\n", sl, sl) // []int, [11 22 13]
在 Go 1.17 之前,不支持将切片转换为数组类型,仅可以通过 unsafe 包以不安全的方式实现转换unsafe 包的安全性没有得到编译器和运行时的保证,尽量不要使用
1234sl := []int{11, 12, 13}var p = (*[3]int)(unsafe.Pointer(&sl[0]))p[1] += 10fmt.Printf("%v\n", sl) // [11 22 13]
从 Go 1.17 开始,支持从切片转换为数组类型指针
1234sl := []int{11, 12, 13}var p = (*[3]int)(sl)p[1] += 10fmt.Printf("%v\n ...
Go - Sync + Atomic
并发模型
Do not communicate by sharing memory; instead, share memory by communicating.
Go 应用并发设计的主流风格:使用 channel 进行不同 goroutine 间的通信
sync:提供基于共享内存并发模型的低级同步原语,如互斥锁、读写锁、条件变量等
atomic:提供原子操作原语
Sync场景
需要高性能的临界区同步机制场景 - critical section
channel 是一种高级同步原语,其自身的实现是构建在低级同步原语的基础上
因此,channel 自身的性能要略逊于低级同步原语,开销更大
12345678910111213141516171819202122232425262728293031323334353637383940414243var cs = 0 // critical sectionvar mu sync.Mutexvar c = make(chan struct{}, 1)func criticalSectionSyncByMutex() { ...
Go - Channel
一等公民
Go 在语法层面将 channel 作为一等公民对待
可以像使用普通变量那样使用 channel
定义 channel 类型变量、给 channel 变量赋值
将 channel 作为参数传递给函数或者方法
将 channel 作为返回值从函数或者方法中返回
将 channel 发送到其它 channel
基本用法make
与 slice、struct、map 一样,channel 是复合数据类型,即声明 channel 类型变量时,必须给出具体的元素类型channel 类型变量在声明时,如果没有被赋予初值,默认值为 nil,即 nil channel(读写都阻塞)
12// 声明一个元素为 int 类型的 channel 类型变量var ch chan int
slice、struct、map 都支持使用复合类型字面值作为变量初始值,channel 类型变量赋初值的唯一方法是 make
12ch1 := make(chan int) // 无缓冲ch2 := make(chan int, 5) // 有缓冲,缓冲大小为 5
send / receive
chan ...
Go - GPM
CPU
一个 Go 程序对于操作系统来说只是一个用户层程序,操作系统眼中只有线程
goroutine 的调度由 Go Runtime 来完成:『公平』竞争『CPU』
Go Runtime 如何将众多的 goroutine 按照一定的算法调度到『CPU』上运行
goroutine 要竞争的『CPU』是操作系统线程
将 goroutine 按照一定算法放到不同的操作系统线程上执行
演进
G-M -> G-P-M
不支持抢占 -> 支持协作式抢占 -> 支持基于信号的异步抢占
G-M
2012.03.28 Go 1.0
抽象
每个 goroutine 对应于运行时的一个抽象结构 G
被视作『CPU』的操作系统线程,则对应另一个抽象结构 M(machine)
工作
将 G 调度到 M 上去运行
GOMAXPROCS
调度器可见的最大 M 数
缺陷:限制 Go 并发程序的伸缩性,尤其对于有高吞吐或并行计算的服务程序
由于单一全局互斥锁和集中状态存储的存在,导致所有 goroutine 相关操作,都需要上锁
M 之间经常传递可运行的 goroutine,导致调用延 ...
Go - CSP
并发 vs 并行单核单进程
内部仅有一条代码执行流,不存在竞态,无需考虑同步问题
每个单进程应用对应一个操作系统进程
操作系统的多个进程按照时间片大小,被轮流调度到单核上执行
单核在某个时刻只能执行一个进程对应的程序代码,两个进程不存在并行执行的可能
并行(Parallelism):在同一时刻,有两个或两个以上的进程的代码在处理器上执行
因此,多处理器或者多核处理器是并行执行的必要条件
多进程
应用结构清晰,维护性更好
应用通过 fork 等系统调用创建多个子进程,共同实现应用的功能
App1 内部划分为多个模块,每个模块用一个进程来承载执行,每个模块都是一个单独的执行流
在单核下,多进程依然无法并行执行,只能按照时间片被操作系统调度到单核上执行
多核多线程
进程并不适合用于承载采用了并发设计的应用的模块执行流
进程是操作系统中资源分配的基本单位:应用代码+应用数据、操作系统资源(文件描述符、内存地址空间等)
因此,进程的创建、切换和撤销的代价都很大
线程是运行于进程上下文中的更轻量的执行流
随着处理器技术的发展,多核处理器成为了主流,让真正的并行成为了可能
基于线程的应用通常采 ...