golang入门实践(vscode远程开发及调试配置、代码自动提示和跳转)
0、和java一样,go也是跨平台,天生支持Unicode。但是go直接生成可执行文件,性能更高,内存占用少。但是又和java一样,go打出来的二进制包能够扫描到依赖的库,如果库有漏洞,安全扫描会被扫出来。
1、安装、环境配置及术语
从https://golang.google.cn/下载对应的版本。
使用命令行go env可以查看,如下:
[lightdb@lightdb-dev ~]$ go env GO111MODULE="" #设置是否打开go modules,auto/on/off三个取值,1.13开始默认,1.14开始推荐 GOARCH="amd64" GOBIN="" # go install最终拷贝到的目录,一般go程序打成tar.gz分发,所以关系不大,配置的话指向$GOPATH/bin GOCACHE="/home/lightdb/.cache/go-build" GOENV="/home/lightdb/.config/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOINSECURE="" GOMODCACHE="/home/lightdb/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/home/lightdb/go" # 项目的根目录,最重要的环境变量 GOPRIVATE="" GOPROXY="https://goproxy.cn,direct" # GOPROXY是用来设置go mod的代理,以英文逗号“,”分割,使Go在后续拉取模块版本时能够脱离传统的VCS方式从镜像站点快速拉取。它拥有一个默认:https://proxy.golang.org,direct,但proxy.golang.org在中国无法访问,所以建议使用goproxy.cn替代 GOROOT="/usr/lib/golang" # golang的安装目录,跟JAVA_HOME一样 GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/lib/golang/pkg/tool/linux_amd64" GOVCS="" GOVERSION="go1.20.6" GCCGO="gccgo" GOAMD64="v1" AR="ar" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="/dev/null" GOWORK="" CGO_CFLAGS="-O2 -g" CGO_CPPFLAGS="" CGO_CXXFLAGS="-O2 -g" CGO_FFLAGS="-O2 -g" CGO_LDFLAGS="-O2 -g" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build261630818=/tmp/go-build -gno-record-gcc-switches"
hello world验证环境及熟悉目录
[lightdb@lightdb-dev go]$ cd src/example/
[lightdb@lightdb-dev example]$ go mod init
[lightdb@lightdb-dev go]$ go run src/example/first.go 123 hello world 65 12.345 [lightdb@lightdb-dev go]$ pwd /home/lightdb/go [lightdb@lightdb-dev go]$
[lightdb@lightdb-dev bin]$ export | grep GO declare -x GOBIN="//home/lightdb/go/bin" declare -x GOPATH="/home/lightdb/go" declare -x GOROOT="//home/lightdb/go/go"
这样脚手架就可以运行了。
[lightdb@lightdb-dev src]$ cd go-web/
[lightdb@lightdb-dev go-web]$ go build ginweb.go # go build也是个很大的范畴,可以参考https://blog.csdn.net/wan212000/article/details/124288970
相关的报错及原因和解决办法如下:
[lightdb@lightdb-dev go]$ go build go: go.mod file not found in current directory or any parent directory; see 'go help modules' [lightdb@lightdb-dev go]$ go mod init # 理论上go mod init即可 go: cannot determine module path for source directory /home/lightdb/go (outside GOPATH, module path must be specified) Example usage: 'go mod init example.com/m' to initialize a v0 or v1 module 'go mod init example.com/m/v2' to initialize a v2 module Run 'go help mod init' for more information. [lightdb@lightdb-dev go]$ go mod init example.com/m # 可以提前预建目录,这样cd dir; go mod init即可,不用指定名称 go: creating new go.mod: module example.com/m go: to add module requirements and sums: go mod tidy [lightdb@lightdb-dev go]$ go build $GOPATH/go.mod exists but should not # 使用go module模块化机制后,go.mod需要放在具体的模块中而非在GOPATH目录下,如$GOPATH/src/example/go.mod,再运行即可 [lightdb@lightdb-dev go]$ go install $GOPATH/go.mod exists but should not
[lightdb@lightdb-dev go]$ go run src/go-web/ginweb.go src/go-web/ginweb.go:7:2: no required module provides package github.com/gin-gonic/gin: go.mod file not found in current directory or any parent directory; see 'go help modules' src/go-web/ginweb.go:5:2: package tool/controller is not in GOROOT (/usr/lib/golang/src/tool/controller) # 需要在模块的根目录下打包和运行
package first is not in GOROOT (/usr/lib/golang/src/first) # 在$GOPATH目录下执行 ln -s /usr/lib/golang go
1.1 主要文件
go.mod Go Modules 的核心文件,包含module、require、replace 和 exclude 4部分。
go.sum 供 Go 命令在构建时判断依赖包是否合法
详见https://zhuanlan.zhihu.com/p/635696935,讲得很清楚了。
1.2 依赖关系
和java一样,go的依赖管理也经过了多次构造,最新为2019年的go mode。
模块是go管理依赖的基本单元,模块由多个package组成。go.mod 文件定义了模块的名称及其依赖包,通过导入路径和版本描述一个依赖。
[lightdb@lightdb-dev go]$ go help modules Modules are how Go manages dependencies. A module is a collection of packages that are released, versioned, and distributed together. Modules may be downloaded directly from version control repositories or from module proxy servers. For a series of tutorials on modules, see https://golang.org/doc/tutorial/create-module. For a detailed reference on modules, see https://golang.org/ref/mod. By default, the go command may download modules from https://proxy.golang.org. It may authenticate modules using the checksum database at https://sum.golang.org. Both services are operated by the Go team at Google. The privacy policies for these services are available at https://proxy.golang.org/privacy and https://sum.golang.org/privacy, respectively. The go command's download behavior may be configured using GOPROXY, GOSUMDB, GOPRIVATE, and other environment variables. See 'go help environment' and https://golang.org/ref/mod#private-module-privacy for more information.
1.3 golang工具链
从Go 1.21开始,Go发行版由一个go命令和一个捆绑的Go工具链组成,后者是标准库以及编译器、汇编器和其他工具。go命令可以使用其捆绑的Go工具链以及它在本地PATH或根据需要下载的其他版本。正在使用的Go工具链的选择取决于GOTOOLCHAIN环境设置和主模块的go.mod文件或当前工作区的go.work文件中的go和工具链行。当您在不同的主模块和工作区之间移动时,正在使用的工具链版本可能会有所不同,就像模块依赖版本一样。
如果指定的工具链版本和go版本不一致,则会出现version "go1.23.3" does not match go tool version "go1.23.4"错误,工具链版本太新(各种原因都可能导致)。更新GOTOOLCHAIN为go版本,删除各种go.mod、go.work中的toolchain行(以为它比go行的优先级更高),删除GOPATH、GOROOT下的pkg,重新编译即可。
即:在标准配置中,当工具链至少与主模块或工作区中的go或工具链行一样新时,go命令使用其自己的捆绑工具链。例如,当在表示go1.21.0的主模块中使用与Go1.21.3捆绑的go命令时,go命令使用Go1.21.3。当go或工具链行比捆绑的工具链更新时,go命令运行较新的工具链。例如,当在表示go1.21.9的主模块中使用与Go1.21.3捆绑的go命令时,go命令会查找并运行Go1.21.9。它首先在PATH中查找名为go1.21.9的程序,否则会下载并缓存Go1.21.9工具链的副本。可以禁用这种自动工具链切换,但在这种情况下,为了更精确的向前兼容性,go命令将拒绝在其中go行需要更新版本的Go的主模块或工作区中运行。也就是说,go行设置使用模块或工作区所需的最低Go版本。
go: module golang.org/x/tools/gopls@v0.17.1 requires go >= 1.23.1 (running go 1.22.10; GOTOOLCHAIN=go1.22.10)
将GOTOOLCHAIN设置为和go的版本一样即可。尝试降级gopls不可行,即使执行了go install golang.org/x/tools/gopls@v0.16.0到了指定版本,还是报该错。
go toolchain原理解密
2、IDE配置(vscode为例)及调试
vscode支持远程开发和本地开发,attach/launch都一样,都支持本地和远程。所以除非要访问linux资源,否则直接windows开发也可以,这和c/c++语言需要依赖很多posix兼容库如pthread.h/uinstd.h/sys/select.h等具有很大的差别。
2.1 工程结构推荐
一般来说,一个公司多个模块,下面的结构就行。
GOPATH #环境变量配置 bin pkg src com.xxx 顶级域名作为区分,便于和三方引入包保持一致 lightdb main.go base service1 service2 unisql main.go base service1 service2 infra module1 module2
go以包为组织单位,虽然分目录,但包名和目录名不一定要相同(只是习惯上最后一层相同),而且包是单层如packageName而非com.xxx.packageName。一个文件夹下的所有文件必须具有相同的包名声明。
import的是路径名而非包名(因为一个目录下的所有文件需要声明为一个包,所以import目录就相当于import包),引用的是GOPATH(跟CLASSPATH很像)开始的相对路径。需要注意的是如果目录里面有子目录,则不是同一个包,这个和java是一样的。
如果多个目录下存在同名的package(这可是必不可免的),需要在import这些目录时为每个目录指定一个package的别名。
2.3.1 大型工程编译
对于大型工程,推荐使用makefile进行编译,如下:
BINARY=myapp GOOS ?= $(shell go env GOOS) GOARCH ?= $(shell go env GOARCH) build-linux: GOOS=linux GOARCH=amd64 go build -o $(BINARY)-$(GOOS)-$(GOARCH) build-osx: GOOS=darwin GOARCH=amd64 go build -o $(BINARY)-$(GOOS)-$(GOARCH)
make build-linux
2.2、代码辅助、跳转及格式化配置
安装gopls (Go languageserver),go插件。正确的设置GOPATH和GOROOT,最好GOTOOLCHAIN也设置。
2.3、debug和attach配置
安装手册插件。然后安装Go Outliner(outline显示函数列表)
go get -u github.com/go-delve/delve/cmd/dlv 执行后还是这个报错。
到 launch.json 文件设置 "--check-go-version=false",这个错倒是不报了,但是堆栈报错如下:
...
....
安装指定版本的dlv,如1.23后,vscode调试正常。
[lightdb@hs199 unisql_cli]$ go install github.com/go-delve/delve/cmd/dlv@v1.23.0 go: downloading github.com/go-delve/delve v1.23.0 go: downloading github.com/sirupsen/logrus v1.9.3 go: downloading github.com/spf13/cobra v1.7.0 go: downloading golang.org/x/sys v0.17.0 go: downloading github.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62 go: downloading golang.org/x/arch v0.6.0 go: downloading github.com/hashicorp/golang-lru v1.0.2 go: downloading github.com/cilium/ebpf v0.11.0 go: downloading go.starlark.net v0.0.0-20231101134539-556fd59b42f6 go: downloading github.com/cpuguy83/go-md2man/v2 v2.0.2 [lightdb@hs199 unisql_cli]$ dlv version Delve Debugger Version: 1.23.0 Build: $Id: e673f2da02185a1c2b6dea52842b0a8c7e7a03b9 $
2.3.1 配置launch.json
{ "version": "0.2.0", "configurations": [ { "name": "gotest", "type": "go", "request": "launch", "mode": "debug", "program": "/home/lightdb/zjhua-dev-src/LTSQL/unisql/sql-convert-runtime-go/unisql_cli/main.go", "dlvFlags": [ "--check-go-version=false" ], "env": { "GOPATH": "/home/lightdb/gpath" }, "args": [], "showLog": true } ] }
2.3.2 标准库和三方库能够跳转,业务代码无法跳转,如下:
原因:vscode识别到GOPATH配置的地址不是工程主目录(可通过命令面板go to GOPATH和export | grep GOPATH或go env检查),这是其一。其二,源代码中package名称和目录名不一致,比如package叫做abc/bcc/ffe,目录是xxx/bcc/ffe,就会出现找不到的情况,所以package名最好和路径名保持一致。
Error loading workspace: err: exit status 2: stderr: go: no such tool "compile" : packages.Load error
解决方法:
3、基本概念
每个 Go 程序都是由包构成的。程序从 main
包开始运行。
一个程序中可以有很多main包,这个main包之间无法相互调用,运行时通过go run xxx.go运行。
"math/rand"
包中的源码均以 package rand
语句开始。
go里面只有包和函数级别的变量,不存在对象级别。 也就是没有对象。
go里面,类型名在变量名之后,函数返回值在最后(这和java/c++相反),如下:
package main import "fmt" func add(x int, y int) int { // 有点类似scala,和java/c都不一样 return x + y } func main() { fmt.Println(add(42, 13)) }
函数可以返回多值,这个和主流编程语言(如java/c/c++)存在明显的差异,虽然c++在<utility>中支持返回pair带两个值。
支持string、bool和无符号类型(和java/c的差别,java不支持无符号,c在17之前不支持布尔,一般模式typedef定义1和0来模拟),类型转换需要强制写,比如int到float,跟postgresql的强类型一样。而且转换是函数调用式的语法target_type(src_val)而不是java/c里面的(target_type) src_val。如下:
package main import ( "fmt" "math" ) func main() { var x, y int = 3, 4 var f float64 = math.Sqrt(float64(x*x + y*y)) // float64是类型转换 var z uint = uint(f) fmt.Println(x, y, z) }
for/if/else/switch更像是pl/sql的用法加上另外一半的c/c++/java,必须大括号、条件不必强制括号,switch不用带break(因为内置)。支持将某个语句延迟到函数调用(defer)返回后在执行(有点aop after的概念哈)
package main import "fmt" func main() { defer fmt.Println("world") fmt.Println("hello") }
函数内外语句使用差异明显,var和:=
package main import "fmt" func main() { var i, j int = 1, 2 k := 3 // 函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。 c, python, java := true, false, "no!" fmt.Println(i, j, k, c, python, java) }
包成员可见性,大写字母开头为默认导出,小写则不导出,因为大部分开发喜欢默认小写标识符,所以其实对于控制可访问性设计比java/c++可更妥些。
init()函数
和c/c++里面dlopen()加载so文件后自动执行_init()函数一样,init()函数在每次import一个包后自动执行。如果import相互依赖,也不会重复加载,每个包只会被初始化一次。
基本类型以外的高级数据类型
指针(虽然支持,但基本不推荐使用,除非为了效率),go中非简单类型也是传值、并且没有对象java的概念,没有c++的传引用,所以指针不可避免还是会用的多。
结构体(go不支持类,结构体上支持定义方法),结构体上的方法(按照go官方说法:方法只是个带接收者参数的函数)语法略微怪异,如下:
package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }
支持接口类型(也就是interface的概念)
type Abser interface { Abs() float64 }
可变长度数组(切片实现,也支持数组的数组)
在 Go 语言中,数组和切片(slice)是两种不同的数据类型,它们的行为和用途也有所不同。Go 语言设计上更倾向于使用切片而不是直接操作数组,原因如下:
1. 数组是固定长度的,而切片是动态长度的
数组的长度是固定的,定义时需要指定长度(例如
[5]int
),且长度不能改变。切片是动态长度的,它是对数组的抽象,可以动态增长或缩小(例如
[]int
)。由于数组的长度固定,直接操作数组会限制灵活性。而切片提供了动态长度的能力,更适合处理不确定长度的数据。
2. 数组是值类型,切片是引用类型
数组是值类型。当数组作为参数传递给函数时,会复制整个数组的内容,这可能会导致性能问题(尤其是数组较大时)。
切片是引用类型。切片本身是一个轻量级的数据结构,包含指向底层数组的指针、长度和容量。当切片作为参数传递时,只会传递切片的元数据(指针、长度和容量),而不会复制底层数组的数据。
// 数组示例
func modifyArray(arr [5]int) { arr[0] = 100 // 修改的是数组的副本 } func main() { a := [5]int{1, 2, 3, 4, 5} modifyArray(a) fmt.Println(a) // 输出: [1 2 3 4 5],原数组未被修改 }
// 切片示例 func modifySlice(s []int) { s[0] = 100 // 修改的是底层数组 } func main() { a := []int{1, 2, 3, 4, 5} modifySlice(a) fmt.Println(a) // 输出: [100 2 3 4 5],原切片被修改 }
所以,Go 语言更倾向于使用切片而不是直接操作数组,主要是因为切片提供了更高的灵活性、更好的性能和更方便的操作接口。虽然可以直接操作数组,但在大多数场景下,切片是更合适的选择。
map(原生支持)
支持函数作为参数和返回值(像函数指针或c++里面的function对象),此时函数支持闭包的概念(其实就是中间变量)。
上面两点更像c with object
协程(goroutine和channel):并发的核心是:不用使用共享内存来通信,而是用通信(channel)来共享内存。
5、go自动化回归单元测试
默认情况下,go的单元测试文件放在和源代码一样的目录下,运行go test会对所有的xxx_test.go进行测试,如下:
[lightdb@lightdb-dev go-web]$ go test
? tool [no test files]
这是一个比较大的主题,可以参考有赞paas的做法,https://cloud.tencent.com/developer/article/1684515,其中还结合了和sonar报告的集成
https://before80.github.io/go_docs_with_hugo/goBlog/2023/CodeCoverageForGoIntegrationTests/
https://go.dev/blog/integration-test-coverage go 1.20在go build上增加了-cover选项,支持集成测试统计
对于集成测试,建议搭建一个专门的平台维护用例输出和输入,自动化回归和对比,开发、测试一体化,跟postgresql的make check/make checkworld一样。
java jacoco是一个可以单元测试和集成测试均覆盖的覆盖率工具,支持java agent模式统计。输出也很完整。
https://blog.csdn.net/d_chunyu/article/details/117136293
6、makefile不仅可用于c/c++,还可以用于go。也就是makefile可用于任何linux下的开发,跟语言无关。尤其是go涉及到部分代码使用c/c++开发时更是如此。
7、命令行程序设计
go提供了一个设计命令行程序的框架cobra cli(Kubernetes, Docker, Hugo,pgcenter均基于它)
8、go web开发之gin框架
9、go访问数据库之postgresql,gorm
10、模块分发
go build可以用来打包整个模块
go install将其安装到$GOBIN目录下
https://stackoverflow.com/questions/59741795/how-to-distribute-a-go-module-with-c-dependencies
10.1 go build与go build xxx.go的区别
go build会自动查找目录下main函数,并进行遍历查找所有依赖。go build xxx.go只会编译指定的文件,多个需要穷举列出。前者适合打可执行文件,后者适合打so和普通package。
10.2 go clean
go clean -cache 清理缓存,加-x选项打印明细。
10.3 构建so共享库
go一共可生成三种类型的so:一种是供c和java调用的标准linux/windows动态共享库,第二种是供go编译时链接的动态库,第三种是插件,本质上也是动态库,只不过只有编译为插件,才能跟c/c++一样通过dlopen加载动态库。
-buildmode=c-shared,主要供c/java调用。
-buildmode=shared,go build的时候,使用-linkshared构建。
-buildmode=plugin,它必须符合下列条件:
- 编译的时候
-buildmode=plugin
- 包声明必须为
package main
。 - 要导出(函数名或变量名首字母大写)需要被主程序调用的函数或变量,不一定非得是interface,只不过一般来说将其封装为接口,便于管理生命周期和多态可插拔,多为拦截器或hook机制。
示例可参考https://eli.thegreenplace.net/2021/plugins-in-go/。
10.4 条件编译
适合于跨平台开发。
一个源文件中可以有多个 build tags
,同一行的逗号隔开的 tag
之间是 逻辑与
的关系,空格隔开的 tag
之间是 逻辑或
的关系, 不同行之间的 tag
是 逻辑与
的关系。
# 逻辑或,此源文件只能在 linux 或者 darwin 平台下编译 // +build linux darwin # 逻辑与,此源文件只能在 linux/amd64 平台下编译 // +build amd64 // +build linux # 此源文件只能在 linux/386 或者 darwin 平台下编译 // +build linux,386 darwin
//go:build linux,amd64 // 这个文件只会当目标平台是 Linux amd64 的时候被编译和执行。如果目标是其他平台,这个文件将被忽略。 package main import "fmt" func main() { fmt.Println("This code only runs on Linux AMD64.") }
注意://go:build 替代了早期版本 Go 中的 // +build,在 Go 1.17 及以后的版本中推荐使用 //go:build。Go1.19 新增了支持 `go:build unix`。
编译通过-tags指定,跟c/c++编译的-D宏选项一样。
$ go run -tags debug main.go
通过 条件编译
,可以在构建服务时非常灵活地指定具体的环境、版本、部署方式等参数,真正做到 一套代码,随地编译和运行
。
当 go build 或 go test 等命令执行时,它们会检查这些标签来决定哪些文件应该包含在构建过程中。注意:直接go run是不会检查tag的。
11、三方库
日志:logrus
kafka:kafka-go ,https://blog.csdn.net/qq_30614345/article/details/131056586,https://zhuanlan.zhihu.com/p/431434480
json:jsoniter
orm:https://github.com/go-gorm/gorm
sql解析:https://github.com/pingcap/parser,当然也可以用postgresql的c语言sql解析器(性能更强),pgpool-ii就是剥离的psotgresql解析器
11.1 标准库和golang/x库
12、docker中go get无法访问代理,如下:
[root@lightdb-dev pkg]# docker-compose up [+] Building 31.4s (9/9) FINISHED docker:default => [helloweb internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [helloweb internal] load build definition from helloweb.build 0.0s => => transferring dockerfile: 439B 0.0s => [helloweb internal] load metadata for docker.io/library/golang:1.20 10.4s => [helloweb 1/5] FROM docker.io/library/golang:1.20@sha256:bfc60723228b88180b1e15872eb435cf7e6d8199eae9be77c8dfd8f8079343df 0.0s => [helloweb internal] load build context 0.0s => => transferring context: 33B 0.0s => CACHED [helloweb 2/5] WORKDIR /app 0.0s => CACHED [helloweb 3/5] COPY helloweb.go /app 0.0s => CACHED [helloweb 4/5] RUN go mod init helloweb 0.0s => ERROR [helloweb 5/5] RUN go get github.com/go-redis/redis 21.0s ------ > [helloweb 5/5] RUN go get github.com/go-redis/redis: 20.99 go: module github.com/go-redis/redis: Get "https://proxy.golang.org/github.com/go-redis/redis/@v/list": dial tcp 142.251.43.17:443: connect: connection refused ------ failed to solve: process "/bin/sh -c go get github.com/go-redis/redis" did not complete successfully: exit code: 1
解决方法:docker.build中,使用RUN指定GOPROXY,如下:
# 导入依赖的Redis go module RUN go mod init helloweb RUN go env -w GO111MODULE=on RUN go env -w GOPROXY=https://goproxy.cn,direct RUN go get github.com/go-redis/redis
再运行docker compose up即可。
查看某个模块依赖的包清单
https://blog.csdn.net/aaqq123456654321/article/details/127113393
go性能分析
pgo,pprof
go与c/c++交互 cgo及环境变量
参见https://mp.weixin.qq.com/s/yYlbSLva--kDN24EO8f3rA。
内存管理
gc
打印堆栈及性能
https://www.51cto.com/article/748837.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2023-01-14 lightdb中日期加减
2017-01-14 使用javassist运行时动态重新加载java类及其他替换选择
2017-01-14 mongodb 最佳可视化工具mongobooster