go-入坑笔记
go-入坑笔记.
代码实践仓库: https://github.com/yangxuan0261/GoLab
前篇
- Go 入门指南 - http://wiki.jikexueyuan.com/project/the-way-to-go/
- Go 语言圣经 - https://books.studygolang.com/gopl-zh/ch0/ch0-01.html
- 对pkg里面的针对每个函数写代码例子 - https://github.com/astaxie/gopkg
- 通用教程 - https://github.com/astaxie/build-web-application-with-golang
- 由一个经验丰富的Go程序员群体编写的一系列Go学习范例 - https://github.com/mkaz/working-with-go/tree/master/pages
- Go社区投票选举出来的最好的在线 Go 教程 - https://hackr.io/tutorials/learn-golang
- Go by Example - https://gobyexample.com/
- 教程清单 - https://github.com/yinggaozhen/awesome-go-cn
- go 常用的设计模式 - https://github.com/tmrts/go-patterns
- Go 语言优秀资源整理,为项目落地加速 - https://github.com/shockerli/go-awesome, https://shockerli.net/post/go-awesome/
- Golang conference PPT - https://github.com/gopherchina/conference
- 支持300+常用功能的开源GO语言工具函数库 - https://www.toutiao.com/article/7095733166278918670
规范
- Uber Go 语言编码规范 - https://github.com/xxjwxc/uber_go_guide_cn
- Go语言编码规范指导 - https://zhuanlan.zhihu.com/p/63250689
go 1.14 发布说明
- 关于Go1.14,你一定想知道的性能提升与新特性 - https://studygolang.com/articles/26529
编辑器 选择 Goland
其他不用考虑了. 参考: go-编辑器GoLand.md
新建一个项目
新建一个文件夹 E:\public_gopath
, 用来作为公共的 GOPATH, 设置为 系统环境变量, 所有的 go 项目都可以引用到这里, 就不用重复下载库了
linux 安装 go
下载安装包: go1.15.3.linux-amd64.tar.gz. 传送门: https://golang.google.cn/dl/
解压
1
2
3$ mv go1.15.3.linux-amd64.tar.gz /opt
$ cd /opt
$ tar -xzvf go1.15.3.linux-amd64.tar.gz创建
GOPATH
工作区目录, app 代码就放在该目录下1
$ mkdir -p ~/go-workspace/src
配置环境变量
1
2
3
4
5
6
7
8
9$ vim ~/.bash_profile // 添加一下环境变量
export GOROOT=/opt/go
export GOPATH=$HOME/go-workspace
# export GOPROXY=https://goproxy.io
export GOPROXY=https://goproxy.cn,direct
export PATH=$PATH:$GOROOT/bin
$ source ~/.bash_profile // 使之生效
升级 go
直接删除原来的 go 目录, 将新版本的 go 解压出来即可
1
2$ rm -fr /opt/go
$ tar -xzvf go1.15.3.linux-amd64.tar.gz -C /opt项目内的模块升级
1
2
3
4
5
6
7
8$ go mod tidy
$ go mod vendor
2. done. 查看版本
```json
$ go version
go version go1.15.3 linux/amd64
Goland 方式
新建一个 go app, 文件夹名为 go-lab
用 goland 直接打开这个 go-lab 文件夹, 就可以开始编写项目了. 默认会使用环境变量中的 GOPATH
docker 方式
只要将 micro 路径挂载进去即可, 然后进入 golang docker 实例中 编译/运行. 参考: docker_golang使用.md
Go 语言风格指南
- https://github.com/uber-go/guide/blob/master/style.md
- 中文版: https://github.com/xxjwxc/uber_go_guide_cn
命名
项目: 用
-
分割单词, 全小写. 如:go-mars
(GitHub 上的开源项目都是这样命名)包名
- 全部小写。没有大写或下划线。
- 大多数使用命名导入的情况下,不需要重命名。
- 简短而简洁。请记住,在每个使用的地方都完整标识了该名称。
- 不用复数。例如
net/url
,而不是net/urls
。 - 不要用“common”,“util”,“shared”或“lib”。这些是不好的,信息量不足的名称。
protobuf 命名
package
全小写, 单词使用
.
分割. 如1
2
3
4
5// hello.proto
package go.micro.srv.greeter;
// 生成的 hello.pb.go
package go_micro_srv_greetermessage 和 字段 都使用 驼峰 写法, 首字母大写
1
2
3
4
5
6
7message HelloReq {
string NameById = 1;
}
message HelloRsp {
string NameById = 1;
}
性能
将原语转换为字符串或从字符串转换时,
strconv
速度比fmt
快。尽量初始化时指定 Map 容量
1
make(map[T1]T2, hint)
相似的声明放在一组, import, var, const 都一样.
1
2
3
4import (
"a"
"b"
)nil 是一个有效的 slice. 零值切片(用
var
声明的切片)可立即使用,无需调用make()
创建。1
2
3
4
5
6
7
8
9var nums []int
if add1 {
nums = append(nums, 1)
}
func aaa() []int {
return nil
}字符串 string format.
1
2
3
4
5
6
7// bad
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
// good
const msg = "unexpected values %v, %v\n" // 这样就不用每次分配 字符串 栈内存
fmt.Printf(msg, 1, 2)
函数分组与顺序
由于函数是按接收者分组的,因此普通工具函数应在文件末尾出现。
1 | type something struct{ ... } |
其他
使用原始字符串字面值,避免转义, “ ` “ 来表示原生字符串
1
2wantError := "unknown name:\"test\"" // bad
wantError := `unknown error:"test"` // goode
优秀开源项目
- https://zhuanlan.zhihu.com/p/50413709
- https://github.com/hackstoic/golang-open-source-projects
- https://github.com/yinggaozhen/awesome-go-cn (汇总了多个开源项目)
web 框架
- go有哪些快速开发的web框架? - https://www.zhihu.com/question/27370112
用的比较多, star 数量最多的是 gin: https://github.com/gin-gonic/gin
vscode go.toolsGopath
允许将 go get 下载到的工具隔离到一个 go.toolsGopath 指定的目录中
go get 规则
For example, these commands are all valid:
1 | go get -d -v github.com/gorilla/mux@latest # same (@latest is default for 'go get') |
-d
: 只下载不安装-f
: 只有在你包含了 -u 参数的时候才有效,不让 -u 去验证 import 中的每一个都已经获取了,这对于本地 fork 的包特别有用-fix
: 在获取源码之后先运行 fix,然后再去做其他的事情-t
: 同时也下载需要为运行测试所需要的包-u
: 强制使用网络去更新包和它的依赖包-v
: 显示执行的命令
基础语法教程
菜鸟教程: http://www.runoob.com/go/go-environment.html
常用库
- 官网包 - https://godoc.org/
- Golang常用包有哪些?- https://www.zhihu.com/question/22009370
编码规范
构建, 打包, 执行
go build
通过go build加上要编译的Go源文件名,我们即可得到一个可执行文件,默认情况下这个文件的名字为源文件名字去掉.go后缀。
1 | $ go build hellogo.go |
当然我们也 可以通过-o选项来指定其他名字 myfirstgo (这个可执行文件):
1 | $ go build -o myfirstgo hellogo.go |
如果是window平台, 则要导出 xxx.exe 可执行文件
1
$ go build -o myfirstgo.exe hellogo.go
如果我们在go-examples目录下直接执行go build命令,后面不带文件名,我们将得到一个与目录名同名的可执行文件:
1 | $ go build |
指定导出 %GOPATH%/src 下某个文件夹下的程序. 该文件加下的必须有 go文件是有
package main
和func main()
1
E:\GoWinEnv>go build -o hello.exe GoLab/test_file
- 生成的 hello.exe 在执行命令的该文件夹下, 这里就是在 E:\GoWinEnv
构建不同 os 的可执行程序
Golang 支持在一个平台下生成另一个平台可执行程序的交叉编译功能。
Mac下编译Linux, Windows平台的64位可执行程序:
1
2$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build test.go
$ CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build test.goLinux下编译Mac, Windows平台的64位可执行程序:
1
2$ CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build test.go
$ CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build test.goWindows下编译Mac, Linux平台的64位可执行程序:
1
2
3
4
5
6
7
8
9
10
11// mac
$ SET CGO_ENABLED=0
$ SET GOOS=darwin
$ SET GOARCH=amd64
$ go build test.go
// linux
$ SET CGO_ENABLED=0
$ SET GOOS=linux
$ SET GOARCH=amd64
$ go build test.goGOOS:目标平台的操作系统(darwin、freebsd、linux、windows)
GOARCH:目标平台的体系架构(386、amd64、arm)
交叉编译不支持 CGO 所以要禁用它上面的命令编译 64 位可执行程序,你当然应该也会使用 386 编译 32 位可执行程序
很多博客都提到要先增加对其它平台的支持,但是我跳过那一步,上面所列的命令也都能成功,且得到我想要的结果,可见那一步应该是非必须的,或是我所使用的 Go 版本已默认支持所有平台。
go install
与build命令相比,install命令在编译源码后还会将可执行文件或库文件安装到约定的目录下。
- go install编译出的可执行文件以其所在目录名(DIR)命名
- go install将可执行文件安装到与src同级别的bin目录下,bin目录由go install自动创建
- go install将可执行文件依赖的各种package编译后,放在与src同级别的pkg目录下.
go run
直接执行某个 go 文件
1 | $ cd src\GoLab\test_grpc\greeter_client |
参考资料:
面向对象编程
package
同一个目录下不能存在不同包名的文件
import 别的包规则
1
2
3
4
5
6
7package main
import (
pkg001 "GoLab/test_pkg/pkg001" // 重命名别名为 pkg001 在本文件中的使用, 一般不要这样干
_ "GoLab/test_pkg/pkg002" // _ 防止 未被使用的包, 被格式化代码时被编辑器自动干掉这一行
"fmt" // 导入内置包
)- 包名路径: 是 GOPATH/src 为基础搜索.
import 的流程. 参考: https://blog.csdn.net/zhangzhebjut/article/details/25564457
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:
import
var
init()
main()
1
2
3func init() { // 是保留的内置方法, import时自动执行
fmt.Println("--- init")
}
空结构体
空结构体的特点:1、不占用内存;2、地址不变
map 的 value, 变相为 set
1 | empty := struct{}{} |
协程的型号量
1 | var ch chan struct{} |
Go 关键字和 channel 的用法
make
golang分配内存有一个make函数,该函数第一个参数是类型,第二个参数是分配的空间,第三个参数是预留分配空间. 例如a:=make([]int, 5, 10), len(a)输出结果是5,cap(a)输出结果是10,然后对a[4]进行赋值发现是可以得,但对a[5]进行赋值发现报错了,于是郁闷这个预留分配的空间要怎么使用呢,于是google了一下发现原来预留的空间需要重新切片才可以使用,于是做一下记录,代码如下。
1 | func main(){ |
make()分配:内部函数 make(T, args) 的服务目的和 new(T) 不同。
它只生成切片,映射和程道,并返回一个初始化的(不是零)的,type T的,不是 *T 的值。
这种区分的原因是,这三种类型的数据结构必须在使用前初始化.
比如切片是一个三项的描述符,包含数据指针(数组内),长度,和容量;在这些项初始化前,切片为 nil 。
对于切片、映射和程道,make初始化内部数据结构,并准备要用的值。
记住 make() 只用于 映射、切片、程道,不返回指针。要明确的得到指针用 new() 分配。
go 关键字用来创建 goroutine (协程),是实现并发的关键。go 关键字的用法如下:
1 | //go 关键字放在方法调用前新建一个 goroutine 并让他执行方法体 |
因为 goroutine 在多核 cpu 环境下是并行的。如果代码块在多个 goroutine 中执行,我们就实现了代码并行。那么问题来了,怎么拿到并行的结果呢?这就得用 channel 了。
1 | //resultChan 是一个 int 类型的 channel。类似一个信封,里面放的是 int 类型的值。 |
channel 详解
chan 是信号的关键字, 作用有点像c++多线程里面的 signal, 发送信号给其他线程, 通知其可以继续往下跑了.
在 go 里 chan 的使用是结合了 go,select 实现了 goroutine, 多并发.
1 | func test_chan03() { |
channel 读写加持
chan 在函数中定义形参是时可以指定是 读写,只读,只写 三个形式, 作用与 c++ 中 const关键字 差不多
1 | fnRW := func(c chan int) { // c可以读写 |
参考: https://www.cnblogs.com/baiyuxiong/p/4545028.html
channel 赋值不生效
可能没有初始化变量
make(chan string)
defer
defer 的思想类似于C++中的析构函数,不过Go语言中“析构”的不是对象,而是函数,defer就是用来添加函数结束时执行的语句。注意这里强调的是添加,而不是指定,因为不同于C++中的析构函数是静态的,Go中的defer是动态的
defer 中使用匿名函数依然是一个闭包。
1 | func test_defer() { |
defer 还有一个重要的作用是用于 panic 时的 恢复, panic 恢复也只能在 defer 中.
参考: http://wiki.jikexueyuan.com/project/the-way-to-go/13.3.html
参考: 5 年 Gopher 都不知道的 defer 细节,你别再掉进坑里
什么是 defer
defer 是 Go 语言提供的一种用于注册延迟调用的机制,每一次 defer 都会把函数压入栈中,当前函数返回前再把延迟函数取出并执行。
defer 语句并不会马上执行,而是会进入一个栈,函数 return 前,会按先进后出(FILO)的顺序执行。也就是说最先被定义的 defer 语句最后执行。先进后出的原因是后面定义的函数可能会依赖前面的资源,自然要先执行;否则,如果前面先执行,那后面函数的依赖就没有了。
采坑点
使用 defer 最容易采坑的地方是和带命名返回参数的函数一起使用时。
defer 语句定义时,对外部变量的引用是有两种方式的,分别是作为函数参数和作为闭包引用。作为函数参数,则在 defer 定义时就把值传递给 defer,并被缓存起来;作为闭包引用的话,则会在 defer 函数真正调用时根据整个上下文确定当前的值。
避免掉坑的关键是要理解这条语句:
1 | return xxx |
这条语句并不是一个原子指令,经过编译之后,变成了三条指令:
1 | 1.返回值=xxx |
1,3 步才是 return 语句真正的命令,第 2 步是 defer 定义的语句,这里就有可能会操作返回值。
性能
- Go defer 会有性能损耗,尽量不要用?- https://segmentfault.com/a/1190000019490834
补充上柴大的回复:“不是性能问题,defer 最大的功能是 Panic 后依然有效。如果没有 defer,Panic 后就会导致 unlock 丢失,从而导致死锁了”,非常经典。
结论, 性能影响小, 但对于 调用极多 的函数, 能不用就不用.
new make 区别
new
new 这个内置函数,可以给我们分配一块内存让我们使用,但是现实的编码中,它是不常用的。我们通常都是采用短语句声明以及结构体的字面量达到我们的目的,比如:
u:=&user{}
make
make 函数是无可替代的,我们在使用 slice、map 以及 channel 的时候,还是要使用 make 进行初始化,然后才才可以对他们进行操作。
make 返回的还是这三个引用类型本身;而 new 返回的是指向类型的指针。
slice
slice 是容易踩坑的地方
如果要从某个 slice 中切除部分来 appen 新内容, 一定要使用 copy 的方式拷贝出来新的, 不然极其可能会修改到原来 slice 的数据.
1 | arr1 := []int{1, 2, 3} |
CGO
CGO 提供了 golang 和 C 语言相互调用的机制。某些第三方库可能只有 C/C++ 的实现,完全用纯 golang 的实现可能工程浩大,这时候 CGO 就派上用场了。可以通 CGO 在 golang 在调用 C 的接口,C++ 的接口可以用 C 包装一下提供给 golang 调用。被调用的 C 代码可以直接以源代码形式提供或者打包静态库或动态库在编译时链接。推荐使用静态库的方式,这样方便代码隔离,编译的二进制也没有动态库依赖方便发布也符合 golang 的哲学。
goroutine 通过 CGO 进入到 C 接口的执行阶段后,已经脱离了 golang 运行时的调度并且会独占线程,此时实际上变成了多线程同步的编程模型。如果 C 接口里有阻塞操作,这时候可能会导致所有线程都处于阻塞状态,其他 goroutine 没有机会得到调度,最终导致整个系统的性能大大较低。总的来说,只有在第三方库没有 golang 的实现并且实现起来成本比较高的情况下才需要考虑使用 CGO ,否则慎用。
热重启
- Golang中的热重启 - https://cloud.tencent.com/developer/article/1388556
Go 错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
1 | type error interface { |
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
1 | func Sqrt(f float64) (float64, error) { |
在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
1 | result, err:= Sqrt(-1) |
select 语句的行为
1 | // https://talks.golang.org/2012/concurrency.slide#32 |
上面这段代码中,select 语句有四个 case 子语句,前两个是 receive 操作,第三个是 send 操作,最后一个是默认操作。代码执行到 select 时,case 语句会按照源代码的顺序被评估,且只评估一次,评估的结果会出现下面这几种情况:
- 除 default 外,如果只有一个 case 语句评估通过,那么就执行这个case里的语句;
- 除 default 外,如果有多个 case 语句评估通过,那么通过伪随机的方式随机选一个;
- 如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句;
- 如果没有 default,那么 代码块会被阻塞,指导有一个 case 通过评估;否则一直阻塞
以下描述了 select 语句的语法:
每个case都必须是一个通信
所有channel表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行;其他被忽略。
如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
否则:
- 如果有default子句,则执行该语句。
- 如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
接口 interface
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
1 | /* 定义接口 */ |
什么时候需要指针 *
当使用 interface 时不需要 *
, 只有 struct 时才需要 *
1 | type CDog struct { |
context
- Go语言实战笔记(二十)| Go Context - https://www.flysnow.org/2017/05/12/go-in-action-go-context.html
- https://juejin.im/post/5a6873fef265da3e317e55b6
一个是Background
,主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context。
一个是TODO
,它目前还不知道具体的使用场景,如果我们不知道该使用什么Context的时候,可以使用这个。
他们两个本质上都是emptyCtx
结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的Context。
创建 ctx 的四个接口
1
2
3
4func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context这四个
With
函数,接收的都有一个partent参数,就是父Context,我们要基于这个父Context创建出子Context的意思,这种方式可以理解为子Context对父Context的继承,也可以理解为基于父Context的衍生。通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个。
WithCancel
函数,传递一个父 Context作为参数,返回子Context,以及一个取消函数用来取消Context。WithDeadline
函数,和WithCancel
差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消 Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消。WithTimeout
和WithDeadline
基本上一样,一个是 多少时间后 超时自动取消,一个是 什么时间点 超时自动取消。WithValue
函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的 Context,这个绑定的数据可以通过Context.Value
方法访问到
TLS
- 带入gRPC:基于 CA 的 TLS 证书认证 - https://studygolang.com/articles/15331
模块
- Go依赖模块版本之Module避坑使用详解 - https://www.cnblogs.com/sunsky303/p/10710637.html
- go mod 使用 - https://juejin.im/post/5c8e503a6fb9a070d878184a
- Go Modules 详解使用 - https://learnku.com/articles/27401
- Golang Modules ( 建议, 详细 ) - https://leileiluoluo.com/posts/golang-modules.html
自从 Go
官方从去年推出 1.11 之后,增加新的依赖管理模块并且更加易于管理项目中所需要的模块。模块是存储在文件树中的 Go 包的集合,其根目录中包含 go.mod 文件。 go.mod 文件定义了模块的模块路径,它也是用于根目录的导入路径,以及它的依赖性要求。每个依赖性要求都被写为模块路径和特定语义版本。
从 Go 1.11 开始,Go 允许在 $GOPATH/src 外的任何目录下使用 go.mod 创建项目。在 $GOPATH/src 中,为了兼容性,Go 命令仍然在旧的 GOPATH 模式下运行。从 Go 1.13 开始,模块模式将成为默认模式。
环境变量设置
1
2export GO113MODULE=on // 113 是对应 go 的版本 1.13
export GOPROXY=https://goproxy.io // 设置代理 // 可以翻墙的话就不用设置这个代理了如果不生效, 使用这个. 参考: https://www.jianshu.com/p/e0c878d4ca19
1
go env -w GOPROXY=https://goproxy.cn,direct
初始化新模块.
在
$GOPATH/src
之外的任何地方创建一个新的目录1
2F:\a_link_workspace\go\GoWinEnv_Test01\src (master -> origin)
λ mkdir backend && cd backend模块初始化. 命令:
go mod init xxx
. xxx 为模块名 ( 模块名最好和仓库根目录路径一致 ) . 会生成一个模块配置文件 go.mod1
2
3F:\a_link_workspace\go\GoWinEnv_Test01\src\backend (master -> origin)
λ go mod init backend
go: creating new go.mod: module backend查看内容
1
2
3
4
5F:\a_link_workspace\go\GoWinEnv_Test01\src\backend (master -> origin)
λ cat go.mod
module backend
go 1.13
测试. 新建一个 main.go, 引入一个第三方模块
1
2
3
4
5
6
7
8
9
10
11
12
13package main
import "github.com/gin-gonic/gin" // 引入的第三方模块
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}run 一下
1
2F:\a_link_workspace\go\GoWinEnv_Test01\src\backend (master -> origin)
λ go run main.gogo.mod 会自动引入的第三方模块,
1
2
3
4
5
6
7
8module backend
go 1.13
require (
github.com/gin-gonic/gin v1.4.0
github.com/labstack/echo v3.3.10+incompatible // indirect
)
命令
go mod download 下载模块到本地缓存,缓存路径是 $GOPATH/pkg/mod/cache
go mod edit 是提供了命令版编辑 go.mod 的功能,例如 go mod edit -fmt go.mod 会格式化 go.mod
go mod graph 把模块之间的依赖图显示出来
go mod init 初始化模块(例如把原本dep管理的依赖关系转换过来)
go mod tidy 增加缺失的包,移除没用的包
go mod vendor 把依赖拷贝到 vendor/ 目录下
修改 vendor 第三方库使其生效. 参考: 使用本地包
把第三方库指到 vendor 目录下对应的库即可
go mod verify 确认依赖关系
go mod why 解释为什么需要包和模块
使用本地包
- go module使用本地包 - https://segmentfault.com/a/1190000018672890
有些情况下需要修改第三方库进行 调试/打log, 不引用远程包
直接修改 go.mod 文件, 修改 go-plugins 包的指向
1 | require ( |
然后修改本的 go-plugins 中的代码就可以编译进去了.
使用 本地包 有个坑. 因为 mod 模式下引用的包都是按需下载, 所以下载的第三方库并不是所在完整的库. 一旦代码引用到这个库里面的其他 package 时, 就会报找不到的错误.
1 | go: github.com/micro/go-micro@v1.11.1: parsing ..\..\..\..\vendor\github.com\micro\go-micro\go.mod: open f:\a_link_workspace\go\GoWinEnv_new\src\vendor\github.com\micro\go-micro\go.mod: The system cannot find the path specified. |
解决办法是 取消 掉 go.mod 中 指向本地包的代码
1 | // replace github.com/micro/go-micro => ../vendor/github.com/micro/go-micro |
然后在 go run
一下, 就下载, 再 go mod vendor 一下会移到 vendor 目录下
1 | f:\a_link_workspace\go\GoWinEnv_new\src\GoMicro\test_cmd\test_cmd_service\cmd1 (master -> origin) |
正确的姿势
仓库根目录下, 添加一个 子模块, 将 完整的包 下下来丢到 src 目录下
- 如果不需要提交这个 子模块, 在 .gitmodules 中删掉这个 子模块
修改 go.mod 中仓库的 引用包 的指向
1
replace github.com/micro/go-micro => ../github.com/micro/go-micro
这样修改 ../github.com/micro/go-micro 目录下的代码就能编译进去生效了.
检查 项目是否有错误
- 直接在根目录使用命令:
go mod vendor
使用其他版本的包
1 | replace github.com/gogo/protobuf v0.0.0-20190410021324-65acae22fc9 => github.com/gogo/protobuf v0.0.0-20190723190241-65acae22fc9d // 指向其他版本的包 |
发布版本 - 打 tag
- Go module 如何发布 v2 及以上版本? - https://blog.cyeam.com/go/2019/03/12/go-version
导入所有依赖
使用命令 go get -d -v ./...
-d
: 标志只下载代码包,不执行安装命令;-v
: 打印详细日志和调试日志。这里加上这个标志会把每个下载的包都打印出来;./...
: 这个表示路径,代表当前目录下所有的文件。
docker go环境
容器内默认工作区是 /go
, 所以可以挂载到 /go/src
目录下
常见问题
ssh 远程连进去, 不知道为啥 没有go指令, 需要自己添加到环境变量中 .bash_profile
1
2
3export PATH=$PATH:/usr/local/go/bin
GO_BIN=/usr/local/go/bin
export GO_BIN然后使其生效
# source .bash_profile
ubuntu 安装 go
下载 go1.10.3.linux-amd64.tar , 地址:https://golang.google.cn/dl/
解压:
# tar zxvf go1.10.3.linux-amd64.tar.gz -C /usr/local
增加环境变量
1
2
3
4
5
6
7# vi ~/.bash_profile
...
export GOROOT=/usr/local/go
export GOPATH=/mytemp/GoLab # 项目地址
export PATH=$PATH:$GOPATH:/usr/local/go/bin
# source ~/.bash_profile # 使其生效查看命名, ok
1
2# go version
go version go1.10.3 linux/amd64
唯一ID生成
- 分布式系统唯一ID生成方案汇总 - https://www.cnblogs.com/wyb628/p/7189772.html
RabbitMQ
- 我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 - https://www.sojson.com/blog/48.html
反射 reflect
- Go Reflect 性能 - https://colobu.com/2019/01/29/go-reflect-performance/
优化, 可以通过 自定义的生成器脚本, 生成, 避免使用反射
Builder & Option 设计模式
- 实例浅谈利用Golang的Builder&Option设计模式来传递初始化参数 - https://www.toutiao.com/a6768276240735404558/?tt_from=mobile_qq&utm_campaign=client_share×tamp=1575900880&app=news_article&utm_source=mobile_qq&utm_medium=toutiao_ios&req_id=201912092214400100260790191A2D68A8&group_id=6768276240735404558
服务的 ip 和 端口
如果要让 端口 给外部连接, 一定要写成 :8080
的形式, 不要使用 127.0.0.1:8080
, 因为这样指定了只有本机可以连接
正确姿势:
1 | addr := ":6601" |
GC
- 深入理解Go-垃圾回收机制 - https://juejin.im/post/5d78b3276fb9a06b1829e691
并发模型
- Golang并发模型:轻松入门流水线FAN模式 - https://segmentfault.com/a/1190000017182416
- Go并发模型:轻松入门流水线模型 - https://segmentfault.com/a/1190000017142506
数据竞争
- Introducing the Go Race Detector - https://go.dev/blog/race-detector
- 并发编程的数据竞争问题以及解决之道 - https://learnku.com/articles/45279
- Go 并发编程-共享变量 - https://juejin.cn/post/6981014849090224141
很多处在竞态的错误很难发现,Go 语言中提供了一个工具,可以帮忙检查代码中是否存在竞态。使用起来很简单,只需要在以下命令之后加上 -race
参数就可以:
1 | $ go run -race |
加上这个参数之后,编译器会对代码在执行时对所有共享变量的访问,如果发现一个 goroutine 写入一个变量之后,没有任何同步的操作,就有另外一个 goroutine 读写了这个变量,那就说明这里存在竞态,就会报错。比如下面的代码:
1 | data := 1 |
运行 go run -race main.go
之后,会报下面的错误:
1 | Found 1 data race(s) |
文档/注释
- godoc 介绍以及 Golang 注释规范 https://toutiao.io/posts/tdc9jf/preview
方法
可以 一行 或者 两行
1
2
3
4
5// Add 两数相加(这一行会被截取为简短介绍, [方法名 descr] )
// 两数相加的注意事项以及原理(这一行作为超级详细的介绍)
func Add(n1,n2 int)int{
return n1+n2
}
废弃
接口加上
Deprecated
注释1
2// Deprecated: Do not use.
func NewRpc(opts ...Option) *Rpc {编辑器上就可以提示出来
系统标记
参考:
排除 windows 系统
1
2
3
4// +build !windows
package platform
...编辑器上就可以提示出来
generate
生成提示
1
2
3package net // import "v2ray.com/core/common/net"
//go:generate go run v2ray.com/core/common/errors/errorgen要执行的代码
v2ray.com/core/common/errors/errorgen/main.go
1
2
3package main
func main() {}编辑器上 alt + enter 就可以提示出来
常见编译报错
Q: can’t load package: package test: found packages main (base.go) and testgo (test_go.go) in E:\GoLab\src\test
A: 同一个目录下不能存在不同包名的文件
declared and not used
声明但未被使用, 可以这样屏蔽报错
1
2 xf := xiaofang{}
_ = xf
源码阅读
Context 源码剖析
- Context 源码剖析 - https://www.qtmuniao.com/2020/07/12/go-context/
- go context剖析之源码分析 - https://juejin.cn/post/6844903741842259975
time/rate 实现剖析
- Golang 标准库限流器 time/rate 实现剖析 - https://www.cyhone.com/articles/analisys-of-golang-rate/
- Golang 官方限流器time/rate使用 - https://piaohua.github.io/post/golang/20200815-golang-rate-limiter/
踩坑
保存自动格式化, 源代码被自动删除
参考:
https://github.com/microsoft/vscode-go/issues/2604
https://stackoverflow.com/questions/48124565/why-does-vscode-delete-golang-source-on-save
把格式化工具由 goreturns 换成 goformat, 同时取消掉保存自动格式化
1
2
3
4
5
6
7 "go.formatTool": "gofmt", // 使用 gofmt 工具格式化
"go.alternateTools": {
"go-langserver": "gopls"
},
"[go]": {
"editor.formatOnSave": false
},
go get 报错: ‘xxx’ is not using a known version control system
可能有两个原因
- 删除本地的 xxx 目录
- 没有连上 vpn
go run 报错: The system cannot find the path specified.
可能 go.mod 把某个包指向了本地 vendor 里面的包, 而 vendor 里面又没有这个包.
解决办法参考: 使用本地包
报错: all goroutines are asleep - deadlock!
main goroutine 在等一个永远不会来的数据,那整个程序就永远等下去了, 这个时候就会报上述错误
需要用
sync.WaitGroup
来保证程序正常退出. 参考: test_error.go参考:
map 多线程访问加锁
即使是只是访问, 不做 add 或 delete 操作, 都得加锁, 不然会有潜在的错误:
1 | runtime.mapaccess2_fast64(0x77e360, 0xc0000da000, 0xab1, 0x86f1e0, 0xc001325040) |
多线程访问的正确姿势 - 加锁
1 | a.callMu.Lock() |
channel 导致死锁问题
- goroutine 3609 [chan receive]:
- goroutine 3609 [chan send]:
- goroutine 8 [running]:
等问题, 要不是 chan 没被消费, 就是被过度消费. 例如:
1 | // https://juejin.im/post/5ca318e651882543db10d4ce |
编译报错: can’t load package: package xxx: malformed module path “xxx”: missing dot in first path element
模块名与 目录名 不一致, 修改为一致即可, 如: go.mod 中的
module mars
修改为module go_mars
go get 报错: use of internal package ‘xxx’ not allowed
很简单,vendor 文件夹里面的包路径出现计算机多个目录下,例如c:\go\src;d:\myapp\src等文件夹下存在相同的路径,编译器无法决定加载哪个路径下的文件,于是报错. 删掉 对应的 vendor 目录即可.
for range 遍历 取地址的坑
for range 遍历时, 不能去取 value 值的地址, 因为这个 value 并不是 复制拷贝出来的对象
1 | slice := []int{0, 1, 2, 3} |
参考: https://studygolang.com/articles/14176
todo:
- MySQL 中,21 个写 SQL 的好习惯 - https://www.toutiao.com/i6889659521992491524/
- 如何保证APP与API通信安全,TOKEN冒用带来的风险很大? - https://www.wukong.com/answer/6810240995020521735/
- 万字长文深入浅出 Golang Runtime版本演进、调度、内存及实践 - https://www.toutiao.com/i6855166544079487492/
- 图解 Golang 实现 RSA 加密和签名(有示例) - https://www.toutiao.com/i6894041174915416588/
- 也许是最客观、全面的比较 Rust 与 Go,值得你读 - https://www.toutiao.com/i6893670204468003331/
- Golang的协程调度器原理及GMP设计思想? - https://www.toutiao.com/i6797580089891488259/
- goroutine的退出与泄露:如何检测和预防 - https://www.toutiao.com/i6854445231325053447/
- redis知识点全图 - https://www.toutiao.com/w/i1683599883771908/
- 架构:缓存设计 - https://www.toutiao.com/i6684499505179525640/
- 大文件处理
- Golang 超大文件读取的两个方案 - https://learnku.com/articles/23559/two-schemes-for-reading-golang-super-large-files
- GOLANG秒读16GB大文件 - https://zhuanlan.zhihu.com/p/184937550
- 日志监听
- https 是如何工作的 - https://www.ixigua.com/6811468976225255948/