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 内部划分为多个模块,每个模块用一个进程来承载执行,每个模块都是一个单独的执行流
在单核下,多进程依然无法并行执行,只能按照时间片被操作系统调度到单核上执行
多核多线程
进程并不适合用于承载采用了并发设计的应用的模块执行流
进程是操作系统中资源分配的基本单位:应用代码+应用数据、操作系统资源(文件描述符、内存地址空间等)
因此,进程的创建、切换和撤销的代价都很大
线程是运行于进程上下文中的更轻量的执行流
随着处理器技术的发展,多核处理器成为了主流,让真正的并行成为了可能
基于线程的应用通常采 ...
Go - Pattern
前置原则
在实际真正需要的时候才对程序进行抽象
不要为了抽象而抽象
不要为了使用接口而使用接口
解耦或者抽象是有成本的
造成运行效率的下降
影响代码的可读性
组合
如果 C++ 和 Java 是关于类型层次结构和类型分类的语言,那么 Go 是关于组合的语言
组合是 Go 的重要设计哲学之一,而正交性则为组合哲学的落地提供了方便的条件
正交
在计算机技术中,正交性用于表示某种不相依赖性或者解耦性
编程语言中的语法元素和语言特性也存在正交的情况,通过将这些正交的特性组合起来,可以实现更为高级的特性
Go 在语言设计层面提供的正交的语法元素
Key
Value
类型定义是正交的
无类型体系,没有父子类的概念
方法和类型是正交的
方法的本质只是将 receiver 参数作为第一个参数的函数而已
接口与其它语言元素是正交的
接口与它的实现者之间没有显式关联
方式
垂直组合
用在将多个类型通过类型嵌入的方式实现新类型的定义
传统的 OOP 编程语言大多是通过继承的方式构建出类型体系
Go 没有类型体系的概念,Go 通过类型的组合而不是继承让单一类型承载更多的功能 - ...
Go - nil
接口类型静态特性
接口类型变量具有静态类型
在编译阶段保证灵活性的安全
12345func main() { var err error = 1 // err 的静态类型为 error // cannot use 1 (type int) as type error in assignment: // int does not implement error (missing Error method)}
编译器会在编译阶段对所有接口类型变量的赋值操作进行类型检查
如果右值的类型没有实现接口方法集合中的所有方法,会编译报错
动态特性
接口类型变量在运行时存储了右值的真实类型信息(即接口类型变量的动态类型)
拥有与动态语言相近的灵活性
123var err errorerr = errors.New("this is an error")fmt.Printf("%T\n", err) // *errors.errorString
errors/errors.go123func New(text string) ...
Go - interface
概述
接口类型是由 type 和 interface 关键字定义的一组方法集合(唯一确定所代表的接口)称为方法,而非函数,更多是从这个接口的实现者的角度考虑的
1234type MyInterface interface { M1(int) error M2(io.Writer, ...string)}
方法的参数列表中的形参名和返回值列表中的具名返回值,都不作为区分两个方法的凭据
12345// 等价type MyInterface interface { M1(a int) error M2(w io.Writer, args ...string)}
在接口类型中声明的方法必须是具名的,且方法名在这个接口类型的方法集合中是唯一的
类型嵌入:在 Go 1.14 开始,接口类型允许嵌入不同接口类型的方法集合存在交集但需要方法名和函数签名也要保持一致,否则会编译报错
123456789101112131415type I1 interface { M1()}type I2 interface ...
Go - Type Embedding
类型嵌入
在一个类型的定义中嵌入其它类型
接口
语义:方法集合并入
接口类型声明了由一个方法集合代表的接口
12345678910type E interface { M1() M2()}type I interface { M1() M2() M3()}
完全等价:类型嵌入,新接口类型将嵌入的接口类型的方法集合,并入到自身的方法集合中 - 接口组合
123456789type E interface { M1() M2()}type I interface { E M3()}
Go 标准库
io/io.go1234567891011type Reader interface { Read(p []byte) (n int, err error)}type Writer interface { Write(p []byte) (n int, err error)}type Closer interfac ...