go-入坑笔记.
代码实践仓库: https://github.com/yangxuan0261/GoLab
弄好代理, Proxifier 代理到 ssr. 这样命令行才能走到 代理上
ctrl + shift + p, 输入 go install, 选择 go: install/update tools, 然后全选, 点击ok
只要代理弄好了, 全部都能安装成功. 生成的 一些列 exe 会在 bin 目录下
Installing 1 tool at F:\a_link_workspace\go\GoWinEnv_MicroExamples\bin
goimports
...
Installing golang.org/x/tools/cmd/goimports SUCCEEDED
...
All tools successfully installed. You're ready to Go :).
src 下的项目用 模块管理. 参考: [模块依赖 go mod](#模块依赖 go mod)
参考: Go modules support in Visual Studio Code - https://github.com/Microsoft/vscode-go/wiki/Go-modules-support-in-Visual-Studio-Code
micro 文件夹下的才是项目的源码, 其他文件夹都是依赖的第三方库. 所以 micro 才是 git 仓库.
新建一个 go 项目目录 GoEnvMicro, 再建一个子目录 src, src 目录下的 micro 才是真正的项目源码, 这个才需要用 git 版本控制的代码, GitHub 里的 go 开源项目就是这个. (pkg 目录会自动生成)
用 Goland 打开 GoEnvMicro 这个目录作为项目
模块化这个项目源码目录, cd 到 micro 目录下使用命令: go mod initxxx
E:\ws_go\GoEnvMicro\src\micro
$ go mod init micro
go: creating new go.mod: module micro
此时就可以 run 和 build项目了, 但是 Goland 中提示会报错, 且不能跳转到 项目内/第三方库 的符号.
然后就可以跳转到 项目内/第三方库 的符号了
docker 方式就简单很多
只要将 micro 路径挂载进去即可, 然后进入 golang docker 实例中 编译/运行. 参考: docker_golang使用.md
允许将 go get 下载到的工具隔离到一个 go.toolsGopath 指定的目录中
For example, these commands are all valid:
go get -d -v github.com/gorilla/mux@latest # same (@latest is default for 'go get')
go get -d -v github.com/gorilla/mux@v1.6.2 # records v1.6.2
go get -d -v github.com/gorilla/mux@e3702bed2 # records v1.6.2
go get -d -v github.com/gorilla/mux@c856192 # records v0.0.0-20180517173623-c85619274f5d
go get -d -v github.com/gorilla/mux@master # records current meaning of master
-d
: 标志只下载代码包,不执行安装命令;-v
: 打印详细日志和调试日志。这里加上这个标志会把每个下载的包都打印出来;菜鸟教程: http://www.runoob.com/go/go-environment.html
参考:
在 Go语言程序运行时(runtime)实现了一个小型的任务调度器。这套调度器的工作原理类似于操作系统调度线程,Go 程序调度器可以高效地将 CPU 资源分配给每一个任务。传统逻辑中,开发者需要维护线程池中线程与 CPU 核心数量的对应关系。同样的,Go 地中也可以通过 runtime.GOMAXPROCS() 函数做到,格式为:
runtime.GOMAXPROCS(逻辑CPU数量)
这里的逻辑CPU数量可以有如下几种数值:
一般情况下,可以使用 runtime.NumCPU() 查询 CPU 数量,并使用 runtime.GOMAXPROCS() 函数进行设置,例如:
runtime.GOMAXPROCS(runtime.NumCPU())
Go 1.5 版本之前,默认使用的是单核心执行。从 Go 1.5 版本开始,默认执行上面语句以便让代码并发执行,最大效率地利用 CPU。
GOMAXPROCS 同时也是一个环境变量,在应用程序启动前设置环境变量也可以起到相同的作用。
通过go build加上要编译的Go源文件名,我们即可得到一个可执行文件,默认情况下这个文件的名字为源文件名字去掉.go后缀。
$ go build hellogo.go
$ ls
hellogo* hellogo.go
当然我们也 可以通过-o选项来指定其他名字 myfirstgo (这个可执行文件):
$ go build -o myfirstgo hellogo.go
$ ls
myfirstgo* hellogo.go
如果是window平台, 则要导出 xxx.exe 可执行文件
$ go build -o myfirstgo.exe hellogo.go
如果我们在go-examples目录下直接执行go build命令,后面不带文件名,我们将得到一个与目录名同名的可执行文件:
$ go build
$ ls
go-examples* hellogo.go
指定导出 %GOPATH%/src 下某个文件夹下的程序. 该文件加下的必须有 go文件是有 package main
和 func main()
E:\GoWinEnv>go build -o hello.exe GoLab/test_file
与build命令相比,install命令在编译源码后还会将可执行文件或库文件安装到约定的目录下。
直接执行某个 go 文件
$ cd src\GoLab\test_grpc\greeter_client
$ go run test_grpc_cli.go
同一个目录下不能存在不同包名的文件
import 别的包规则
package main
import (
pkg001 "GoLab/test_pkg/pkg001" // 重命名别名为 pkg001 在本文件中的使用, 一般不要这样干
_ "GoLab/test_pkg/pkg002" // _ 防止 未被使用的包, 被格式化代码时被编辑器自动干掉这一行
"fmt" // 导入内置包
)
import 的流程. 参考: https://blog.csdn.net/zhangzhebjut/article/details/25564457
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:
func init() { // 是保留的内置方法, import时自动执行
fmt.Println("--- init")
}
空结构体的特点:1、不占用内存;2、地址不变
map 的 value, 变相为 set
empty := struct{}{}
println("empty len:", unsafe.Sizeof(empty)) // 0, 空结构体的长度为 0, 常用于 map 里面做 value 值, 因为 go 里面集合没有 set, 所以用 map 变相做 set
协程的型号量
var ch chan struct{}
work := func() {
log.Println("--- work")
time.Sleep(time.Second * 3)
<-ch
}
ch = make(chan struct{}, 10)
for i := 0; i < 15; i++ {
ch <- struct{}{}
go work()
}
golang分配内存有一个make函数,该函数第一个参数是类型,第二个参数是分配的空间,第三个参数是预留分配空间. 例如a:=make([]int, 5, 10), len(a)输出结果是5,cap(a)输出结果是10,然后对a[4]进行赋值发现是可以得,但对a[5]进行赋值发现报错了,于是郁闷这个预留分配的空间要怎么使用呢,于是google了一下发现原来预留的空间需要重新切片才可以使用,于是做一下记录,代码如下。
func main(){
a := make([]int, 10, 20)
fmt.Printf("%d, %d\n", len(a), cap(a))
fmt.Println(a)
b := a[:cap(a)]
fmt.Println(b)
}
/*
10, 20
[0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
*/
make()分配:内部函数 make(T, args) 的服务目的和 new(T) 不同。
它只生成切片,映射和程道,并返回一个初始化的(不是零)的,type T的,不是 *T 的值。
这种区分的原因是,这三种类型的数据结构必须在使用前初始化.
比如切片是一个三项的描述符,包含数据指针(数组内),长度,和容量;在这些项初始化前,切片为 nil 。
对于切片、映射和程道,make初始化内部数据结构,并准备要用的值。
记住 make() 只用于 映射、切片、程道,不返回指针。要明确的得到指针用 new() 分配。
go 关键字用来创建 goroutine (协程),是实现并发的关键。go 关键字的用法如下:
//go 关键字放在方法调用前新建一个 goroutine 并让他执行方法体
go GetThingDone(param1, param2);
//上例的变种,新建一个匿名方法并执行
go func(param1, param2) {
}(val1, val2)
//直接新建一个 goroutine 并在 goroutine 中执行代码块
go {
//do someting...
}
因为 goroutine 在多核 cpu 环境下是并行的。如果代码块在多个 goroutine 中执行,我们就实现了代码并行。那么问题来了,怎么拿到并行的结果呢?这就得用 channel 了。
//resultChan 是一个 int 类型的 channel。类似一个信封,里面放的是 int 类型的值。
var resultChan chan int
//将 123 放到这个信封里面,供别人从信封中取用
resultChan <- 123
//从 resultChan 中取值。这个时候 result := 123
result := <- resultChan
chan 是信号的关键字, 作用有点像c++多线程里面的 signal, 发送信号给其他线程, 通知其可以继续往下跑了.
在 go 里 chan 的使用是结合了 go,select 实现了 goroutine, 多并发.
func test_chan03() {
c1 := make(chan string) // 声明 信号
c2 := make(chan string)
go func() { // go 关键字, 新建一个协程跑这个方法
time.Sleep(time.Second * 1)
c1 <- "one" // 往 c1 信号中丢数据, 也就是通知 c1 阻塞的地方可以继续跑了
}()
go func() {
time.Sleep(time.Second * 2)
c2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1: // 阻塞, 等待 c1 信号通知, 如果收到通知, 这跑这个case, 并把数据丢该 msg1
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
fmt.Println("程序结束666")
/*
msg1 := <-c1 表示 阻塞, 等待c1信号通知, 收到通知后把数据 赋值给 msg1. 如果不需要信号中的数据, 可以可以这样写 <-c1
c1 <- "one" 表示 通知 c1 信号阻塞的地方可以继续运行了, 并往里面丢了一个数据 "one"
*/
}
chan 在函数中定义形参是时可以指定是 读写,只读,只写 三个形式, 作用与 c++ 中 const关键字 差不多
fnRW := func(c chan int) { // c可以读写
c <- 6
val := <-c
fmt.Println("val:", val)
}
fnR := func(c <-chan int) { // c只读
// c <- 6 // 报错: send to receive-only type <-chan int
val := <-c
fmt.Println("val:", val)
}
fnW := func(c chan<- int) { // c只写
// <-c // 报错: receive from send-only type chan<- int
c <- 6
}
参考: https://www.cnblogs.com/baiyuxiong/p/4545028.html
可能没有初始化变量
make(chan string)
defer 的思想类似于C++中的析构函数,不过Go语言中“析构”的不是对象,而是函数,defer就是用来添加函数结束时执行的语句。注意这里强调的是添加,而不是指定,因为不同于C++中的析构函数是静态的,Go中的defer是动态的
defer 中使用匿名函数依然是一个闭包。
func test_defer() {
x, y := 1, 2
defer func(a int) {
fmt.Printf("x:%d,y:%d\n", a, y) // y 为闭包引用
}(x) // 复制 x 的值
x += 100
y += 100
fmt.Println(x, y)
}
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 函数真正调用时根据整个上下文确定当前的值。
避免掉坑的关键是要理解这条语句:
return xxx
这条语句并不是一个原子指令,经过编译之后,变成了三条指令:
1.返回值=xxx
2.调用defer函数
3.空的return
1,3 步才是 return 语句真正的命令,第 2 步是 defer 定义的语句,这里就有可能会操作返回值。
补充上柴大的回复:“不是性能问题,defer 最大的功能是 Panic 后依然有效。如果没有 defer,Panic 后就会导致 unlock 丢失,从而导致死锁了”,非常经典。
结论, 性能影响小, 但对于 调用极多 的函数, 能不用就不用.
new
new 这个内置函数,可以给我们分配一块内存让我们使用,但是现实的编码中,它是不常用的。我们通常都是采用短语句声明以及结构体的字面量达到我们的目的,比如: u:=&user{}
make
make 函数是无可替代的,我们在使用 slice、map 以及 channel 的时候,还是要使用 make 进行初始化,然后才才可以对他们进行操作。
make 返回的还是这三个引用类型本身;而 new 返回的是指向类型的指针。
CGO 提供了 golang 和 C 语言相互调用的机制。某些第三方库可能只有 C/C++ 的实现,完全用纯 golang 的实现可能工程浩大,这时候 CGO 就派上用场了。可以通 CGO 在 golang 在调用 C 的接口,C++ 的接口可以用 C 包装一下提供给 golang 调用。被调用的 C 代码可以直接以源代码形式提供或者打包静态库或动态库在编译时链接。推荐使用静态库的方式,这样方便代码隔离,编译的二进制也没有动态库依赖方便发布也符合 golang 的哲学。
goroutine 通过 CGO 进入到 C 接口的执行阶段后,已经脱离了 golang 运行时的调度并且会独占线程,此时实际上变成了多线程同步的编程模型。如果 C 接口里有阻塞操作,这时候可能会导致所有线程都处于阻塞状态,其他 goroutine 没有机会得到调度,最终导致整个系统的性能大大较低。总的来说,只有在第三方库没有 golang 的实现并且实现起来成本比较高的情况下才需要考虑使用 CGO ,否则慎用。
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}
在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
}
// https://talks.golang.org/2012/concurrency.slide#32
select {
case v1 := <-c1:
fmt.Printf("received %v from c1\n", v1)
case v2 := <-c2:
fmt.Printf("received %v from c2\n", v1)
case c3 <- 23:
fmt.Printf("sent %v to c3\n", 23)
default:
fmt.Printf("no one was ready to communicate\n")
}
上面这段代码中,select 语句有四个 case 子语句,前两个是 receive 操作,第三个是 send 操作,最后一个是默认操作。代码执行到 select 时,case 语句会按照源代码的顺序被评估,且只评估一次,评估的结果会出现下面这几种情况:
以下描述了 select 语句的语法:
每个case都必须是一个通信
所有channel表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行;其他被忽略。
如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
否则:
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
当使用 interface 时不需要 *
, 只有 struct 时才需要 *
type CDog struct {
}
func (this *CDog) Run() { // 实现 IDog 的所有接口
fmt.Println("--- run")
}
type IDog interface{
Run()
}
var ptr1 interface{}
ptr1 = &CDog{} // interface 变量接收的是指针, 而不是对象
if myDog, ok := ptr1.(*CDog); ok { // 动态匹配 CDog 指针
fmt.Printf("--- ptr1 is CDog \n") // --- ptr1 is CDog
}
if myDog, ok := ptr1.(IDog); ok { // 动态匹配 IDog 接口, 不需要指针符号 *
fmt.Printf("--- ptr1 is IDog \n") // --- ptr1 is CDog
}
一个是Background
,主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context。
一个是TODO
,它目前还不知道具体的使用场景,如果我们不知道该使用什么Context的时候,可以使用这个。
他们两个本质上都是emptyCtx
结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的Context。
创建 ctx 的四个接口
func 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
方法访问到
参考: https://blog.csdn.net/wangshubo1989/article/details/72478014
报错: 语法没有指定
-> 在 test.proto 指定语法
syntax = "proto2";
package goprotobuf;
报错: --go_out: protoc-gen-go: 系统找不到指定的文件
-> 需要将 go get -u github.com/golang/protobuf/protoc-gen-go
下载的 protoc-gen-go.exe 所在目录加入环境变量
下载 protoc. https://github.com/protocolbuffers/protobuf/releases
下载并生成 protoc-gen-go.exe, protoc-gen-micro.exe 等 Golang for protobuf 插件
$ go get -u -v github.com/golang/protobuf/proto
$ go get -u -v github.com/golang/protobuf/protoc-gen-go
得先安装 grpc. 参考: grpc
执行命令所在路径为 test_grpc
├─test_grpc
│ ├─aaa
│ ├─greeter_client
│ ├─greeter_server
│ └─protos // 所有 .proto 文件
执行命令
protoc --go_out=plugins=grpc:aaa -I .\protos ./protos/helloworld.proto
-I .\protos
: 指定 依赖路径, 因为 proto 文件在同一文件夹内必须是相同 package (相同模块), 如果依赖了其他模块需要指定 -I
参数指定搜索路径
-I
参数, 则会在输出目录 aaa 下直接生成 xxx.pb.go, 不带 -I
参数则会生成多一级目录, 也就是 aaa/protos/xxx.pb.go.\protos\helloworld.proto
: 指定要生成的文件为 protos\helloworld.proto
文件
./protos/*.proto
, 会匹配生成多个 xxx.pb.go 文件./protos/aaa.proto ./protos/bbb.proto
--go_out=plugins=grpc:aaa
: 输出文件 到 aaa (必须已经存在) 目录下, 格式为 go 语言的 grpc 格式
--go_out=protoc-gen-go:bbb
结果: 会在 aaa 文件夹下生成 xxx.pb.go 文件
执行命令. ( protoc-gen-go.exe 已经在环境变量中, 且输出目录 bbb 已经存在 )
protoc -I .\protos\ --go_out=protoc-gen-go:bbb ./protos/helloworld.proto
gogoprotobuf有两个插件可以使用
//gogo
$ go get github.com/gogo/protobuf/protoc-gen-gogo
$ protoc --gogo_out=. *.proto
//gofast
$ go get github.com/gogo/protobuf/protoc-gen-gofast
$ protoc --gofast_out=. *.proto
使用的 proto 的语法必须是3. ( syntax = "proto3";
)
go get -u -v google.golang.org/grpc
$ protoc --go_out=plugins=grpc:. your_pb.proto
也就是 请求->响应 式的调用.
参考: grpc_call
// proto
syntax = "proto3";
package helloworld;
service Greeter{
rpc SayHello (HelloRequest) returns (HelloReply){}
}
message HelloRequest{
string name = 1;
}
message HelloReply{
string message = 1;
}
// go
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
fmt.Printf("--- from cli, say:%s\n", in.Name)
return &pb.HelloReply{Message: "hello " + in.Name}, nil
}
双方都可主动推送流过去
参考: grpc_stream_putget
// proto
syntax = "proto3";//声明proto的版本 只能 是3,才支持 grpc
package pro;
service Greeter {
rpc AllStream (stream StreamReqData) returns (stream StreamResData){}
}
message StreamReqData {
string data = 1;
}
message StreamResData {
string data = 1;
}
// go
//客户端服务端 双向流, req res 都定义了 stream, 必须通过 recv 和 send 接收发送数据, 然后再通过 .xxx 获取属性值
func (this *server) AllStream(allStr pro.Greeter_AllStreamServer) error {
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
for {
data, _ := allStr.Recv()
log.Println("--- srv AllStream recv:", data.Data)
}
wg.Done()
}()
go func() {
for {
allStr.Send(&pro.StreamResData{Data: "--- srv data"})
time.Sleep(time.Second)
}
wg.Done()
}()
wg.Wait()
return nil
}
自从 Go
官方从去年推出 1.11 之后,增加新的依赖管理模块并且更加易于管理项目中所需要的模块。模块是存储在文件树中的 Go 包的集合,其根目录中包含 go.mod 文件。 go.mod 文件定义了模块的模块路径,它也是用于根目录的导入路径,以及它的依赖性要求。每个依赖性要求都被写为模块路径和特定语义版本。
从 Go 1.11 开始,Go 允许在 $GOPATH/src 外的任何目录下使用 go.mod 创建项目。在 $GOPATH/src 中,为了兼容性,Go 命令仍然在旧的 GOPATH 模式下运行。从 Go 1.13 开始,模块模式将成为默认模式。
环境变量设置
export GO113MODULE=on // 113 是对应 go 的版本 1.13
export GOPROXY=https://goproxy.io // 设置代理 // 可以的话就不用设置这个代理了
初始化新模块.
在 $GOPATH/src
之外的任何地方创建一个新的目录
F:\a_link_workspace\go\GoWinEnv_Test01\src (master -> origin)
λ mkdir backend && cd backend
模块初始化. 命令: go mod init xxx
. xxx 为模块名 ( 模块名最好和仓库根目录路径一致 ) . 会生成一个模块配置文件 go.mod
F:\a_link_workspace\go\GoWinEnv_Test01\src\backend (master -> origin)
λ go mod init backend
go: creating new go.mod: module backend
查看内容
F:\a_link_workspace\go\GoWinEnv_Test01\src\backend (master -> origin)
λ cat go.mod
module backend
go 1.13
测试. 新建一个 main.go, 引入一个第三方模块
package 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 一下
F:\a_link_workspace\go\GoWinEnv_Test01\src\backend (master -> origin)
λ go run main.go
go.mod 会自动引入的第三方模块,
module 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 解释为什么需要包和模块
有些情况下需要修改第三方库进行 调试/打log, 不引用远程包
直接修改 go.mod 文件, 修改 go-plugins 包的指向
require (
github.com/micro/go-plugins v1.3.0
...
)
replace github.com/micro/go-plugins => ../github.com/micro/go-plugins // 修改为本地包, 可以使用相对于 go.mod 的相对路径, 也可以使用绝对路径
然后修改本的 go-plugins 中的代码就可以编译进去了.
使用 本地包 有个坑. 因为 mod 模式下引用的包都是按需下载, 所以下载的第三方库并不是所在完整的库. 一旦代码引用到这个库里面的其他 package 时, 就会报找不到的错误.
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 中 指向本地包的代码
// replace github.com/micro/go-micro => ../vendor/github.com/micro/go-micro
然后在 go run
一下, 就下载, 再 go mod vendor 一下会移到 vendor 目录下
f:\a_link_workspace\go\GoWinEnv_new\src\GoMicro\test_cmd\test_cmd_service\cmd1 (master -> origin)
$ go run main.go
$ go mod vendor
仓库根目录下, 添加一个 子模块, 将 完整的包 下下来丢到 src 目录下
修改 go.mod 中仓库的 引用包 的指向
replace github.com/micro/go-micro => ../github.com/micro/go-micro
这样修改 …/github.com/micro/go-micro 目录下的代码就能编译进去生效了.
replace github.com/gogo/protobuf v0.0.0-20190410021324-65acae22fc9 => github.com/gogo/protobuf v0.0.0-20190723190241-65acae22fc9d // 指向其他版本的包
require (
github.com/golang/protobuf v1.3.2
)
使用命令 go get -d -v ./...
-d
: 标志只下载代码包,不执行安装命令;-v
: 打印详细日志和调试日志。这里加上这个标志会把每个下载的包都打印出来;./...
: 这个表示路径,代表当前目录下所有的文件。官网地址: https://hub.docker.com/_/golang/
容器内默认工作区是 /go
, 所以可以挂载到 /go/src
目录下
ssh 远程连进去, 不知道为啥 没有go指令, 需要自己添加到环境变量中 .bash_profile
export PATH=$PATH:/usr/local/go/bin
GO_BIN=/usr/local/go/bin
export GO_BIN
然后使其生效 # source .bash_profile
下载 go1.10.3.linux-amd64.tar , 地址:https://golang.google.cn/dl/
解压: # tar zxvf go1.10.3.linux-amd64.tar.gz -C /usr/local
增加环境变量
# vi ~/.bash_profile
...
export GOROOT=/usr/local/go
export GOPATH=/mytemp/GoLab # 项目地址
export PATH=$PATH:$GOPATH:/usr/local/go/bin
# source ~/.bash_profile # 使其生效
查看命名, ok
# go version
go version go1.10.3 linux/amd64
优化, 可以通过 自定义的生成器脚本, 生成, 避免使用反射
Q: can’t load package: package test: found packages main (base.go) and testgo (test_go.go) in E:\GoLab\src\test
A: 同一个目录下不能存在不同包名的文件
声明但未被使用, 可以这样屏蔽报错
xf := xiaofang{} _ = xf
参考:
https://github.com/microsoft/vscode-go/issues/2604
https://stackoverflow.com/questions/48124565/why-does-vscode-delete-golang-source-on-save
把格式化工具由 goreturns 换成 goformat, 同时取消掉保存自动格式化
"go.formatTool": "gofmt", // 使用 gofmt 工具格式化 "go.alternateTools": { "go-langserver": "gopls" }, "[go]": { "editor.formatOnSave": false },
可能有两个原因
- 删除本地的 xxx 目录
- 没有连上
可能 go.mod 把某个包指向了本地 vendor 里面的包, 而 vendor 里面又没有这个包.
解决办法参考: 使用本地包
main goroutine 在等一个永远不会来的数据,那整个程序就永远等下去了, 这个时候就会报上述错误
需要用
sync.WaitGroup
来保证程序正常退出. 参考: test_error.go参考:
https://stackoverflow.com/questions/26927479/go-language-fatal-error-all-goroutines-are-asleep-deadlock
https://cloud.tencent.com/developer/article/1418106
即使是只是访问, 不做 add 或 delete 操作, 都得加锁, 不然会有潜在的错误:
runtime.mapaccess2_fast64(0x77e360, 0xc0000da000, 0xab1, 0x86f1e0, 0xc001325040)
多线程访问的正确姿势 - 加锁
a.callMu.Lock()
if ci, ok := a.callMap[pbData.Header.ReqID]; ok {
ci.ch <- pbData
}
a.callMu.Unlock()
等问题, 要不是 chan 没被消费, 就是被过度消费. 例如:
// https://juejin.im/post/5ca318e651882543db10d4ce
// 测试死锁
func Test_goroutinueDeadLock(t *testing.T) {
ch := make(chan int)
ch <- 5
}
/*
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
testing.(*T).Run(0xc0000a8100, 0x5615e7, 0x17, 0x567ba0, 0x47c601)
goroutine 6 [chan send]:
GoLab/test_channel.Test_goroutinueDeadLock(0xc0000a8100)
F:/a_link_workspace/go/GoWinEnv_new/src/GoLab/test_channel/channel_test.go:228 ch <- 5
*/