Go基础
Contents
💠
-
- 4.1. 通过字符串调用指定函数
💠 2024-11-21 10:49:19
Go
官网 | 镜像官网 | Github Repo | Go Doc Rethinking Visual Programming with Go Goplus
Go 项目结构规范
Go Modules
自 1.11 开始支持 Wiki
配置
go env -w GOSUMDB=off
关闭官方 sum 校验服务
配置国内源
|
|
-
go mod init moduleName
按名字初始化模块- 注意,如果想通过
go get URL
方式进行安装,就必须使用代码托管的完整地址, 不需要就可以简化包名
- 例如
module github.com/{username}/{repo}/path/to
- 注意,如果想通过
-
go mod edit -replace github.com/kuangcp/gobase/cuibase=./../cuibase
- go.mod文件会新增:
replace github.com/kuangcp/gobase/cuibase => ./../cuibase
- 多模块开发时,使用本地开发的模块取代发布的版本
- go.mod文件会新增:
- fork 别人项目后开发,可用来替换成自己的模块
replace gihub.com/aaa/bbb => gihub.com/ccc/bbb
- go clean -modcache
go mod graph | 列出模块依赖(包含依赖传递) |
go mod tidy | 删除错误或者不使用的modules |
go mod vendor | 生成vendor目录 |
go mod verify | 验证依赖是否正确 |
go mod why | 查找某个依赖项被引入的路径 |
go get
go get golang.org/x/text@latest | 拉取最新的版本(优先择取 tag) |
go get golang.org/x/text@master | 拉取 master 分支的最新 commit |
go get golang.org/x/text@v0.3.2 | 拉取 指定 tag |
go get golang.org/x/text@342b2e | 拉取 指定 commit |
go get github.com/smartwalle/alipay/v3 | 拉取v3版本 设计最坑 |
go get -u | 更新 mod |
go list -m -versions golang.org/x/text | 列出可安装版本 |
go get -insecure | 不对依赖进行verify 常用于内网的依赖 |
go.mod
关键字
- module 指定包的名字(路径)
- require 指定依赖项模块
- replace 替换依赖项模块
- exclude 忽略依赖项模块
注意依赖项后 有 // indirect 标记的意味着是传递依赖项
当有依赖包更换了路径后,可以此方式统一更换: gofmt -w -r '"github.com/dgrijalva/jwt-go" -> "github.com/golang-jwt/jwt"' .
单个Git仓库发布多个包
- go mod init github.com/username/repo-name/{path}
- git tag -a {path}/v1.0.0
例如
|
|
go.work
关键字和go.mod一致, 并追加了use关键字
use指定使用的模块目录,可以使用go work use添加模块,也可以手动修改go.work工作区添加新的模块,在工作区中添加了模块路径,编译时会自动使用use中的本地代码进行编译 replaces替换依赖仓库地址,replaces命令与go.mod指令相同,用于替换项目中依赖的仓库地址,需要注意的是,replaces和use不能同时指定相同的本地代码路径
通常情况下 go.work不提交到git上, 可以让每个开发人员使用不同的构建规则.
但是以下场景, 如果要实现 demo-gui模块 依赖 demo/util模块 下的 api.go, 有两种方式:
- demo/
- util/
- api.go
- demo-gui/
- main.go
- go.mod
- go.work
- go.mod
- util/
replace 方式
: demo-gui 中的 go.mod 显示依赖一个假版本 然后replace到本地目录1 2
require demo v1.0.0 replace demo v1.0.0 => ../
go.work 方式
: 依赖父目录即可. 如果提交该文件到git, 会让依赖管理更简单看起来
, 但是这样就和go.work设计相违背了1 2 3 4
use ( . ../../demo )
现存问题
- 待思考: 如何像Java一样管理多模块的大项目
- 当需要从Github上fork一个包并修改了内容及API后,想给自己其他项目依赖时, 就必须要修改这个包的 go.mod 里的 module 改为自己仓库的url 或者将修改内容通过pr入原仓库,否则就无法被使用
- 这里会带来一个问题,修改的内容可能是定制化的,不适合合入主仓库
模板项目初始化
数据类型
类型后置的设计相关文章
螺旋形(C/C++)和顺序(Go)的声明语法 Why do a lot of programming languages put the type after the variable name?
string
strings 包 提供了常用字符串API
int
int8 int16 int32 int64 int(位数按操作系统字长而定 32/64)
|
|
Array
Slice
Map
|
|
Set
官方没有提供set类型 可使用社区提供的库 golang-set
基本语法
标准输入输出
- 输出 fmt.Print*
- 输入 fmt.Scan*
- 打印结构体
fmt.Printf("%v\n", object)
时间处理
记住这个神奇的时间 2006-01-02 03:04:05
Go 中不是寻常的 YYYY-mm-dd 这种格式
泛型
自1.18 开始支持
Github: Lightweight anonymous function syntax
讨论可简写的Lambda表达式,类似Js
类型约束
|
|
丑陋设计
不支持成员方法泛型,只支持结构体附加泛型或函数泛型。
- no-parameterized-methods | proposal: spec: allow type parameters in methods
- go是编译型泛型,在编译器期确定所有的类型,跟go的反射冲突,想要解决只能像C#一样运行时支持泛型,或者像java用类型擦除,这个目前来看基本不可能
- 导致了 map reduce 的库简洁的实现比较困难. Github: go stream
个人实现
泛型类型的值不能为nil
- 导致了零值具有歧义
1 2 3 4 5 6 7 8 9
func a[T any]() T{ // 编译报错 return nil // new(T) 也编译报错 // 只能通过编译器来设置零值。 var zero T return zero }
函数
|
|
参数
函数作为参数传入函数
func doAny(functionName func(string, string)){}
返回值
可以多返回值 元组
defer
类似于 Java 中的 finally 语句 例如
defer openFile.Close()
一个函数中可以定义多个 defer 语句, 执行的顺序是按定义次序的逆序, 也就是栈的概念
常见需要回收的是http请求 defer http.Response.Body.Close()
如果不Close会同时影响客户端和服务端资源泄漏
接口
Channel
协程
刘丹冰Aceld 的博客
Golang 调度器 GMP 原理与调度全分析《深入理解 Go 语言》
- 全局队列(Global Queue):存放等待运行的 G。
- P 的本地队列:同全局队列类似,存放的也是等待运行的 G,存的数量有限,不超过 256 个。
- 新建 G’时,G’优先加入到 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 移动到全局队列。
- P 列表:所有的 P 都在程序启动时创建,并保存在数组中,最多有 GOMAXPROCS(可配置 例如automaxprocs) 个。
- M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。
- M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。
- go 程序启动时,会设置 M 的最大数量,默认 10000.
runtime/debug 中的 SetMaxThreads
,设置 M 的最大数量 - 当一个 M 阻塞了,就会创建新的 M。
调度器的设计策略
Goroutine 调度器和 OS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS 调度器负责把内核线程分配到 CPU 的核上执行。
- 复用线程:避免频繁的创建、销毁线程,而是对线程的复用。
- work stealing 机制: 当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程, 类似于Java的Fork/Join的工作窃取。
- hand off 机制: 当本线程M因为 G 进行系统调用阻塞时,线程M释放绑定的 P,把 P 转移给其他空闲的线程M执行。
- 并行:GOMAXPROCS 设置 P 的数量,最多有 GOMAXPROCS 个线程分布在多个 CPU 上同时运行。GOMAXPROCS 也限制了并发的程度
- 比如 GOMAXPROCS = 核数/2,则最多利用了一半的 CPU 核进行并行。
- 抢占:在 coroutine 中要等待一个协程主动让出 CPU 才执行下一个协程,在 Go 中,一个 goroutine 最多占用 CPU 10ms,防止其他 goroutine 被饿死,这就是 goroutine 不同于 coroutine 的一个地方。
- 全局 G 队列:在新的调度器中依然有全局 G 队列,但功能已经被弱化了,当 M 执行 work stealing 从其他 P 偷不到 G 时,它可以从全局 G 队列获取 G。
线程的切换频率,基本取决于线程的数量,使用协程,需要指定每个线程的任务,同样的任务量,协程需要的线程数量应该始终高于自动分配的线程池。 因而:
- 使用线程 = 线程切换开销(小)
- 使用协程 = 线程切换开销(大)+ 协程切换开销
然后CPU开销:
- 线程的指令周期 = 中断检测 + 指令执行(包括取指、转换和执行)
- 协程的指令周期 = 中断检测 + 指令执行 + 中断检测 + 协程信号检测
所以:性能上,io多路复用 + 线程池是完全碾压协程的;协程胜在使用方便
思考如何实现:可中断的调度任务,池式限流(限制最大活跃任务数)
序列化
JSON
结构体必须是大写字母开头的成员才会被处理(大写字母开头才有对外权限)
|
|
忽略空字段
- 字段是指针类型 且注明 omitempty
|
|
应用
文件操作
递归读取当前目录的文件
|
|
http
优化 golang net/http client 客户端存在的性能瓶颈
http.Client 中 Transport对于连接池使用的锁太多
Test
Debug
pprof
|
|
分析内存
- go tool pprof -alloc_space/-inuse_space http://ip:8899/debug/pprof/heap 后进入REPL 输入top查看内存占用
- go tool pprof -inuse_space -cum -svg http://ip:8899/debug/pprof/heap > heap_inuse.svg 导出成svg图
- go tool pprof -http=:7778 http://localhost:8899/debug/pprof/heap
分析CPU
Flame Graphs for Go With pprof CPU火焰图
自动方式 推荐
go tool pprof -raw 'http://localhost:8080/debug/pprof/profile?seconds=20'
得到采样文件 *.pb.gzgo tool pprof -http=: 采样文件
http可指定端口 例如 :2345- 访问 web 地址 菜单 View 下的 Flame Graph
手工方式
- 先 Clone https://github.com/brendangregg/FlameGraph
go tool pprof -raw -output=cpu.txt 'http://localhost:8080/debug/pprof/profile?seconds=20'
./stackcollapse-go.pl cpu.txt | flamegraph.pl > flame.svg
部署
静态编译
- CGO_ENABLED=0 go build
- go build -ldflags ‘-linkmode “external” -extldflags “-static”’
打包的二进制文件在alpine中无法运行
报错: /bin/sh: ./appName: not found
方案: CGO_ENABLED=0 go build
打开文件数超出限制或者tcp连接未及时关闭
报错: cannot assign requested address
方案: ulimit -n 10000 && ./app
常用库
- groupcache 分布式cache
- zlsgo
Web应用脚手架
Tips
lorca
H5 + chromium + Golang
桌面端
interface{} 类型判断nil
- vo == nil || (reflect.ValueOf(vo).Kind() == reflect.Ptr && reflect.ValueOf(vo).IsNil())
字符串计算差异 diffmatchpatch
通过字符串调用指定函数
Author Kuangcp
LastMod 2018-12-14