Go_入坑笔记
title: go-入坑筆記
categories: Go
tags: [go, 編程]
date: 2018-08-02 20:16:18
comments: false
go-入坑筆記.
代碼實(shí)踐倉(cāng)庫(kù): https://github.com/yangxuan0261/GoLab
前篇
- Go 入門指南 - http://wiki.jikexueyuan.com/project/the-way-to-go/
- Go 語(yǔ)言圣經(jīng) - https://books.studygolang.com/gopl-zh/ch0/ch0-01.html
- 對(duì)pkg里面的針對(duì)每個(gè)函數(shù)寫代碼例子 - https://github.com/astaxie/gopkg
- 通用教程 - https://github.com/astaxie/build-web-application-with-golang
- 由一個(gè)經(jīng)驗(yàn)豐富的Go程序員群體編寫的一系列Go學(xué)習(xí)范例 - https://github.com/mkaz/working-with-go/tree/master/pages
- Go社區(qū)投票選舉出來(lái)的最好的在線 Go 教程 - https://hackr.io/tutorials/learn-golang
- Go by Example - https://gobyexample.com/
- 教程清單 - https://github.com/yinggaozhen/awesome-go-cn
環(huán)境配置 (廢棄, 使用 Goland 編碼體驗(yàn)更好)
windows vscode 配置 (2019.10.10)
弄好代理, Proxifier 代理到 ssr. 這樣命令行才能走到 代理上
ctrl + shift + p, 輸入 go install, 選擇 go: install/update tools, 然后全選, 點(diǎn)擊ok
只要代理弄好了, 全部都能安裝成功. 生成的 一些列 exe 會(huì)在 bin 目錄下
Installing 1 tool at F:\a_link_workspace\go\GoWinEnv_MicroExamples\bingoimports...Installing golang.org/x/tools/cmd/goimports SUCCEEDED ...All tools successfully installed. You're ready to Go :).src 下的項(xiàng)目用 模塊管理. 參考: [模塊依賴 go mod](#模塊依賴 go mod)
vscode 模塊化支持
參考: Go modules support in Visual Studio Code - https://github.com/Microsoft/vscode-go/wiki/Go-modules-support-in-Visual-Studio-Code
工程模板
- https://github.com/yangxuan0261/GoWinEnv_Template
新建一個(gè)項(xiàng)目
micro 文件夾下的才是項(xiàng)目的源碼, 其他文件夾都是依賴的第三方庫(kù). 所以 micro 才是 git 倉(cāng)庫(kù).
- windows 上使用 Goland 打開 GoEnvMicro 作為工程
- Linux 中使用 docker 的話, 只要將 micro 路徑掛載進(jìn)去
Goland 方式
新建一個(gè) go 項(xiàng)目目錄 GoEnvMicro, 再建一個(gè)子目錄 src, src 目錄下的 micro 才是真正的項(xiàng)目源碼, 這個(gè)才需要用 git 版本控制的代碼, GitHub 里的 go 開源項(xiàng)目就是這個(gè). (pkg 目錄會(huì)自動(dòng)生成)
用 Goland 打開 GoEnvMicro 這個(gè)目錄作為項(xiàng)目
模塊化這個(gè)項(xiàng)目源碼目錄, cd 到 micro 目錄下使用命令: go mod initxxx
E:\ws_go\GoEnvMicro\src\micro $ go mod init micro go: creating new go.mod: module micro此時(shí)就可以 run 和 build項(xiàng)目了, 但是 Goland 中提示會(huì)報(bào)錯(cuò), 且不能跳轉(zhuǎn)到 項(xiàng)目?jī)?nèi)/第三方庫(kù) 的符號(hào).
項(xiàng)目條狀符號(hào), 添加項(xiàng)目 GOPATH (最好除一下系統(tǒng)的 GOPATH)
import "micro/tool" // 就不會(huì)報(bào)錯(cuò)了alt + enter 快捷鍵 go get 第三方庫(kù)
然后就可以跳轉(zhuǎn)到 項(xiàng)目?jī)?nèi)/第三方庫(kù) 的符號(hào)了
docker 方式
docker 方式就簡(jiǎn)單很多
只要將 micro 路徑掛載進(jìn)去即可, 然后進(jìn)入 golang docker 實(shí)例中 編譯/運(yùn)行. 參考: docker_golang使用.md
Go 語(yǔ)言風(fēng)格指南
- https://github.com/uber-go/guide/blob/master/style.md
優(yōu)秀開源項(xiàng)目
- https://zhuanlan.zhihu.com/p/50413709
- https://github.com/hackstoic/golang-open-source-projects
- https://github.com/yinggaozhen/awesome-go-cn (匯總了多個(gè)開源項(xiàng)目)
vscode go.toolsGopath
- https://github.com/Microsoft/vscode-go/wiki/GOPATH-in-the-VS-Code-Go-extension
允許將 go get 下載到的工具隔離到一個(gè) go.toolsGopath 指定的目錄中
go get 規(guī)則
- https://tip.golang.org/cmd/go/#hdr-Module_queries
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 : 標(biāo)志只下載代碼包,不執(zhí)行安裝命令;
- -v : 打印詳細(xì)日志和調(diào)試日志。這里加上這個(gè)標(biāo)志會(huì)把每個(gè)下載的包都打印出來(lái);
基礎(chǔ)語(yǔ)法教程
菜鳥教程: http://www.runoob.com/go/go-environment.html
golang 多核調(diào)度
參考:
- 對(duì)golang多核編程的一點(diǎn)了解 - https://studygolang.com/articles/9686
- Go語(yǔ)言中的多核調(diào)度 - https://blog.csdn.net/InsZVA/article/details/54081605
- Golang 的 協(xié)程調(diào)度機(jī)制 與 GOMAXPROCS 性能調(diào)優(yōu) - https://juejin.im/post/5b7678f451882533110e8948
GOMAXPROCS
在 Go語(yǔ)言程序運(yùn)行時(shí)(runtime)實(shí)現(xiàn)了一個(gè)小型的任務(wù)調(diào)度器。這套調(diào)度器的工作原理類似于操作系統(tǒng)調(diào)度線程,Go 程序調(diào)度器可以高效地將 CPU 資源分配給每一個(gè)任務(wù)。傳統(tǒng)邏輯中,開發(fā)者需要維護(hù)線程池中線程與 CPU 核心數(shù)量的對(duì)應(yīng)關(guān)系。同樣的,Go 地中也可以通過(guò) runtime.GOMAXPROCS() 函數(shù)做到,格式為:
runtime.GOMAXPROCS(邏輯CPU數(shù)量)
這里的邏輯CPU數(shù)量可以有如下幾種數(shù)值:
- <1:不修改任何數(shù)值。
- =1:單核心執(zhí)行。
- >1:多核并發(fā)執(zhí)行。
一般情況下,可以使用 runtime.NumCPU() 查詢 CPU 數(shù)量,并使用 runtime.GOMAXPROCS() 函數(shù)進(jìn)行設(shè)置,例如:
runtime.GOMAXPROCS(runtime.NumCPU())Go 1.5 版本之前,默認(rèn)使用的是單核心執(zhí)行。從 Go 1.5 版本開始,默認(rèn)執(zhí)行上面語(yǔ)句以便讓代碼并發(fā)執(zhí)行,最大效率地利用 CPU。
GOMAXPROCS 同時(shí)也是一個(gè)環(huán)境變量,在應(yīng)用程序啟動(dòng)前設(shè)置環(huán)境變量也可以起到相同的作用。
常用庫(kù)
- 官網(wǎng)包 - https://godoc.org/
- Golang常用包有哪些?- https://www.zhihu.com/question/22009370
編碼規(guī)范
- https://gocn.vip/article/1
構(gòu)建, 打包, 執(zhí)行
go build
通過(guò)go build加上要編譯的Go源文件名,我們即可得到一個(gè)可執(zhí)行文件,默認(rèn)情況下這個(gè)文件的名字為源文件名字去掉.go后綴。
$ go build hellogo.go $ ls hellogo* hellogo.go當(dāng)然我們也 可以通過(guò)-o選項(xiàng)來(lái)指定其他名字 myfirstgo (這個(gè)可執(zhí)行文件):
$ go build -o myfirstgo hellogo.go $ ls myfirstgo* hellogo.go-
如果是window平臺(tái), 則要導(dǎo)出 xxx.exe 可執(zhí)行文件
$ go build -o myfirstgo.exe hellogo.go
如果我們?cè)趃o-examples目錄下直接執(zhí)行g(shù)o build命令,后面不帶文件名,我們將得到一個(gè)與目錄名同名的可執(zhí)行文件:
$ go build $ ls go-examples* hellogo.go-
指定導(dǎo)出 %GOPATH%/src 下某個(gè)文件夾下的程序. 該文件加下的必須有 go文件是有 package main 和 func main()
E:\GoWinEnv>go build -o hello.exe GoLab/test_file- 生成的 hello.exe 在執(zhí)行命令的該文件夾下, 這里就是在 E:\GoWinEnv
go install
與build命令相比,install命令在編譯源碼后還會(huì)將可執(zhí)行文件或庫(kù)文件安裝到約定的目錄下。
- go install編譯出的可執(zhí)行文件以其所在目錄名(DIR)命名
- go install將可執(zhí)行文件安裝到與src同級(jí)別的bin目錄下,bin目錄由go install自動(dòng)創(chuàng)建
- go install將可執(zhí)行文件依賴的各種package編譯后,放在與src同級(jí)別的pkg目錄下.
go run
直接執(zhí)行某個(gè) go 文件
$ cd src\GoLab\test_grpc\greeter_client $ go run test_grpc_cli.go參考資料:
- http://tonybai.com/2012/08/17/hello-go/
面向?qū)ο缶幊?/h3>
- https://blog.csdn.net/zhangjg_blog/article/details/18790965
package
-
同一個(gè)目錄下不能存在不同包名的文件
-
import 別的包規(guī)則
package mainimport (pkg001 "GoLab/test_pkg/pkg001" // 重命名別名為 pkg001 在本文件中的使用, 一般不要這樣干_ "GoLab/test_pkg/pkg002" // _ 防止 未被使用的包, 被格式化代碼時(shí)被編輯器自動(dòng)干掉這一行"fmt" // 導(dǎo)入內(nèi)置包
)
- 包名路徑: 是 GOPATH/src 為基礎(chǔ)搜索.
-
import 的流程. 參考: https://blog.csdn.net/zhangzhebjut/article/details/25564457
程序的初始化和執(zhí)行都起始于main包。如果main包還導(dǎo)入了其它的包,那么就會(huì)在編譯時(shí)將它們依次導(dǎo)入。有時(shí)一個(gè)包會(huì)被多個(gè)包同時(shí)導(dǎo)入,那么它只會(huì)被導(dǎo)入一次(例如很多包可能都會(huì)用到fmt包,但它只會(huì)被導(dǎo)入一次,因?yàn)闆]有必要導(dǎo)入多次)。當(dāng)一個(gè)包被導(dǎo)入時(shí),如果該包還導(dǎo)入了其它的包,那么會(huì)先將其它包導(dǎo)入進(jìn)來(lái),然后再對(duì)這些包中的包級(jí)常量和變量進(jìn)行初始化,接著執(zhí)行init函數(shù)(如果有的話),依次類推。等所有被導(dǎo)入的包都加載完畢了,就會(huì)開始對(duì)main包中的包級(jí)常量和變量進(jìn)行初始化,然后執(zhí)行main包中的init函數(shù)(如果存在的話),最后執(zhí)行main函數(shù)。下圖詳細(xì)地解釋了整個(gè)執(zhí)行過(guò)程:
-
import
-
var
-
init()
-
main()
func init() { // 是保留的內(nèi)置方法, import時(shí)自動(dòng)執(zhí)行fmt.Println("--- init")
}
空結(jié)構(gòu)體
同一個(gè)目錄下不能存在不同包名的文件
import 別的包規(guī)則
package mainimport (pkg001 "GoLab/test_pkg/pkg001" // 重命名別名為 pkg001 在本文件中的使用, 一般不要這樣干_ "GoLab/test_pkg/pkg002" // _ 防止 未被使用的包, 被格式化代碼時(shí)被編輯器自動(dòng)干掉這一行"fmt" // 導(dǎo)入內(nèi)置包 )- 包名路徑: 是 GOPATH/src 為基礎(chǔ)搜索.
import 的流程. 參考: https://blog.csdn.net/zhangzhebjut/article/details/25564457
程序的初始化和執(zhí)行都起始于main包。如果main包還導(dǎo)入了其它的包,那么就會(huì)在編譯時(shí)將它們依次導(dǎo)入。有時(shí)一個(gè)包會(huì)被多個(gè)包同時(shí)導(dǎo)入,那么它只會(huì)被導(dǎo)入一次(例如很多包可能都會(huì)用到fmt包,但它只會(huì)被導(dǎo)入一次,因?yàn)闆]有必要導(dǎo)入多次)。當(dāng)一個(gè)包被導(dǎo)入時(shí),如果該包還導(dǎo)入了其它的包,那么會(huì)先將其它包導(dǎo)入進(jìn)來(lái),然后再對(duì)這些包中的包級(jí)常量和變量進(jìn)行初始化,接著執(zhí)行init函數(shù)(如果有的話),依次類推。等所有被導(dǎo)入的包都加載完畢了,就會(huì)開始對(duì)main包中的包級(jí)常量和變量進(jìn)行初始化,然后執(zhí)行main包中的init函數(shù)(如果存在的話),最后執(zhí)行main函數(shù)。下圖詳細(xì)地解釋了整個(gè)執(zhí)行過(guò)程:
import
var
init()
main()
空結(jié)構(gòu)體的特點(diǎn):1、不占用內(nèi)存;2、地址不變
map 的 value, 變相為 set
empty := struct{}{}println("empty len:", unsafe.Sizeof(empty)) // 0, 空結(jié)構(gòu)體的長(zhǎng)度為 0, 常用于 map 里面做 value 值, 因?yàn)?go 里面集合沒有 set, 所以用 map 變相做 set協(xié)程的型號(hào)量
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() }Go 關(guān)鍵字和 channel 的用法
make
golang分配內(nèi)存有一個(gè)make函數(shù),該函數(shù)第一個(gè)參數(shù)是類型,第二個(gè)參數(shù)是分配的空間,第三個(gè)參數(shù)是預(yù)留分配空間. 例如a:=make([]int, 5, 10), len(a)輸出結(jié)果是5,cap(a)輸出結(jié)果是10,然后對(duì)a[4]進(jìn)行賦值發(fā)現(xiàn)是可以得,但對(duì)a[5]進(jìn)行賦值發(fā)現(xiàn)報(bào)錯(cuò)了,于是郁悶這個(gè)預(yù)留分配的空間要怎么使用呢,于是google了一下發(fā)現(xiàn)原來(lái)預(yù)留的空間需要重新切片才可以使用,于是做一下記錄,代碼如下。
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()分配:內(nèi)部函數(shù) make(T, args) 的服務(wù)目的和 new(T) 不同。
它只生成切片,映射和程道,并返回一個(gè)初始化的(不是零)的,type T的,不是 *T 的值。
這種區(qū)分的原因是,這三種類型的數(shù)據(jù)結(jié)構(gòu)必須在使用前初始化.
比如切片是一個(gè)三項(xiàng)的描述符,包含數(shù)據(jù)指針(數(shù)組內(nèi)),長(zhǎng)度,和容量;在這些項(xiàng)初始化前,切片為 nil 。
對(duì)于切片、映射和程道,make初始化內(nèi)部數(shù)據(jù)結(jié)構(gòu),并準(zhǔn)備要用的值。
記住 make() 只用于 映射、切片、程道,不返回指針。要明確的得到指針用 new() 分配。
go 關(guān)鍵字用來(lái)創(chuàng)建 goroutine (協(xié)程),是實(shí)現(xiàn)并發(fā)的關(guān)鍵。go 關(guān)鍵字的用法如下:
//go 關(guān)鍵字放在方法調(diào)用前新建一個(gè) goroutine 并讓他執(zhí)行方法體 go GetThingDone(param1, param2);//上例的變種,新建一個(gè)匿名方法并執(zhí)行 go func(param1, param2) { }(val1, val2)//直接新建一個(gè) goroutine 并在 goroutine 中執(zhí)行代碼塊 go {//do someting... }因?yàn)?goroutine 在多核 cpu 環(huán)境下是并行的。如果代碼塊在多個(gè) goroutine 中執(zhí)行,我們就實(shí)現(xiàn)了代碼并行。那么問(wèn)題來(lái)了,怎么拿到并行的結(jié)果呢?這就得用 channel 了。
//resultChan 是一個(gè) int 類型的 channel。類似一個(gè)信封,里面放的是 int 類型的值。 var resultChan chan int //將 123 放到這個(gè)信封里面,供別人從信封中取用 resultChan <- 123 //從 resultChan 中取值。這個(gè)時(shí)候 result := 123 result := <- resultChanchannel 詳解
chan 是信號(hào)的關(guān)鍵字, 作用有點(diǎn)像c++多線程里面的 signal, 發(fā)送信號(hào)給其他線程, 通知其可以繼續(xù)往下跑了.
在 go 里 chan 的使用是結(jié)合了 go,select 實(shí)現(xiàn)了 goroutine, 多并發(fā).
channel 讀寫加持
chan 在函數(shù)中定義形參是時(shí)可以指定是 讀寫,只讀,只寫 三個(gè)形式, 作用與 c++ 中 const關(guān)鍵字 差不多
fnRW := func(c chan int) { // c可以讀寫c <- 6val := <-cfmt.Println("val:", val) }fnR := func(c <-chan int) { // c只讀// c <- 6 // 報(bào)錯(cuò): send to receive-only type <-chan intval := <-cfmt.Println("val:", val) }fnW := func(c chan<- int) { // c只寫// <-c // 報(bào)錯(cuò): receive from send-only type chan<- intc <- 6 }參考: https://www.cnblogs.com/baiyuxiong/p/4545028.html
channel 賦值不生效
可能沒有初始化變量 make(chan string)
defer
defer 的思想類似于C++中的析構(gòu)函數(shù),不過(guò)Go語(yǔ)言中“析構(gòu)”的不是對(duì)象,而是函數(shù),defer就是用來(lái)添加函數(shù)結(jié)束時(shí)執(zhí)行的語(yǔ)句。注意這里強(qiáng)調(diào)的是添加,而不是指定,因?yàn)椴煌贑++中的析構(gòu)函數(shù)是靜態(tài)的,Go中的defer是動(dòng)態(tài)的
defer 中使用匿名函數(shù)依然是一個(gè)閉包。
defer 還有一個(gè)重要的作用是用于 panic 時(shí)的 恢復(fù), panic 恢復(fù)也只能在 defer 中.
參考: http://wiki.jikexueyuan.com/project/the-way-to-go/13.3.html
參考: 5 年 Gopher 都不知道的 defer 細(xì)節(jié),你別再掉進(jìn)坑里
什么是 defer
defer 是 Go 語(yǔ)言提供的一種用于注冊(cè)延遲調(diào)用的機(jī)制,每一次 defer 都會(huì)把函數(shù)壓入棧中,當(dāng)前函數(shù)返回前再把延遲函數(shù)取出并執(zhí)行。
defer 語(yǔ)句并不會(huì)馬上執(zhí)行,而是會(huì)進(jìn)入一個(gè)棧,函數(shù) return 前,會(huì)按先進(jìn)后出(FILO)的順序執(zhí)行。也就是說(shuō)最先被定義的 defer 語(yǔ)句最后執(zhí)行。先進(jìn)后出的原因是后面定義的函數(shù)可能會(huì)依賴前面的資源,自然要先執(zhí)行;否則,如果前面先執(zhí)行,那后面函數(shù)的依賴就沒有了。
采坑點(diǎn)
使用 defer 最容易采坑的地方是和帶命名返回參數(shù)的函數(shù)一起使用時(shí)。
defer 語(yǔ)句定義時(shí),對(duì)外部變量的引用是有兩種方式的,分別是作為函數(shù)參數(shù)和作為閉包引用。作為函數(shù)參數(shù),則在 defer 定義時(shí)就把值傳遞給 defer,并被緩存起來(lái);作為閉包引用的話,則會(huì)在 defer 函數(shù)真正調(diào)用時(shí)根據(jù)整個(gè)上下文確定當(dāng)前的值。
避免掉坑的關(guān)鍵是要理解這條語(yǔ)句:
return xxx這條語(yǔ)句并不是一個(gè)原子指令,經(jīng)過(guò)編譯之后,變成了三條指令:
1.返回值=xxx 2.調(diào)用defer函數(shù) 3.空的return1,3 步才是 return 語(yǔ)句真正的命令,第 2 步是 defer 定義的語(yǔ)句,這里就有可能會(huì)操作返回值。
性能
- Go defer 會(huì)有性能損耗,盡量不要用?- https://segmentfault.com/a/1190000019490834
補(bǔ)充上柴大的回復(fù):“不是性能問(wèn)題,defer 最大的功能是 Panic 后依然有效。如果沒有 defer,Panic 后就會(huì)導(dǎo)致 unlock 丟失,從而導(dǎo)致死鎖了”,非常經(jīng)典。
結(jié)論, 性能影響小, 但對(duì)于 調(diào)用極多 的函數(shù), 能不用就不用.
new make 區(qū)別
-
new
new 這個(gè)內(nèi)置函數(shù),可以給我們分配一塊內(nèi)存讓我們使用,但是現(xiàn)實(shí)的編碼中,它是不常用的。我們通常都是采用短語(yǔ)句聲明以及結(jié)構(gòu)體的字面量達(dá)到我們的目的,比如: u:=&user{}
-
make
make 函數(shù)是無(wú)可替代的,我們?cè)谑褂?slice、map 以及 channel 的時(shí)候,還是要使用 make 進(jìn)行初始化,然后才才可以對(duì)他們進(jìn)行操作。
make 返回的還是這三個(gè)引用類型本身;而 new 返回的是指向類型的指針。
CGO
- https://studygolang.com/articles/16812
CGO 提供了 golang 和 C 語(yǔ)言相互調(diào)用的機(jī)制。某些第三方庫(kù)可能只有 C/C++ 的實(shí)現(xiàn),完全用純 golang 的實(shí)現(xiàn)可能工程浩大,這時(shí)候 CGO 就派上用場(chǎng)了。可以通 CGO 在 golang 在調(diào)用 C 的接口,C++ 的接口可以用 C 包裝一下提供給 golang 調(diào)用。被調(diào)用的 C 代碼可以直接以源代碼形式提供或者打包靜態(tài)庫(kù)或動(dòng)態(tài)庫(kù)在編譯時(shí)鏈接。推薦使用靜態(tài)庫(kù)的方式,這樣方便代碼隔離,編譯的二進(jìn)制也沒有動(dòng)態(tài)庫(kù)依賴方便發(fā)布也符合 golang 的哲學(xué)。
goroutine 通過(guò) CGO 進(jìn)入到 C 接口的執(zhí)行階段后,已經(jīng)脫離了 golang 運(yùn)行時(shí)的調(diào)度并且會(huì)獨(dú)占線程,此時(shí)實(shí)際上變成了多線程同步的編程模型。如果 C 接口里有阻塞操作,這時(shí)候可能會(huì)導(dǎo)致所有線程都處于阻塞狀態(tài),其他 goroutine 沒有機(jī)會(huì)得到調(diào)度,最終導(dǎo)致整個(gè)系統(tǒng)的性能大大較低。總的來(lái)說(shuō),只有在第三方庫(kù)沒有 golang 的實(shí)現(xiàn)并且實(shí)現(xiàn)起來(lái)成本比較高的情況下才需要考慮使用 CGO ,否則慎用。
熱重啟
- Golang中的熱重啟 - https://cloud.tencent.com/developer/article/1388556
Go 錯(cuò)誤處理
Go 語(yǔ)言通過(guò)內(nèi)置的錯(cuò)誤接口提供了非常簡(jiǎn)單的錯(cuò)誤處理機(jī)制。
error類型是一個(gè)接口類型,這是它的定義:
我們可以在編碼中通過(guò)實(shí)現(xiàn) error 接口類型來(lái)生成錯(cuò)誤信息。
函數(shù)通常在最后的返回值中返回錯(cuò)誤信息。使用errors.New 可返回一個(gè)錯(cuò)誤信息:
在下面的例子中,我們?cè)谡{(diào)用Sqrt的時(shí)候傳遞的一個(gè)負(fù)數(shù),然后就得到了non-nil的error對(duì)象,將此對(duì)象與nil比較,結(jié)果為true,所以fmt.Println(fmt包在處理error時(shí)會(huì)調(diào)用Error方法)被調(diào)用,以輸出錯(cuò)誤,請(qǐng)看下面調(diào)用的示例代碼:
result, err:= Sqrt(-1)if err != nil {fmt.Println(err) }select 語(yǔ)句的行為
// 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 語(yǔ)句有四個(gè) case 子語(yǔ)句,前兩個(gè)是 receive 操作,第三個(gè)是 send 操作,最后一個(gè)是默認(rèn)操作。代碼執(zhí)行到 select 時(shí),case 語(yǔ)句會(huì)按照源代碼的順序被評(píng)估,且只評(píng)估一次,評(píng)估的結(jié)果會(huì)出現(xiàn)下面這幾種情況:
以下描述了 select 語(yǔ)句的語(yǔ)法:
-
每個(gè)case都必須是一個(gè)通信
-
所有channel表達(dá)式都會(huì)被求值
-
所有被發(fā)送的表達(dá)式都會(huì)被求值
-
如果任意某個(gè)通信可以進(jìn)行,它就執(zhí)行;其他被忽略。
-
如果有多個(gè)case都可以運(yùn)行,Select會(huì)隨機(jī)公平地選出一個(gè)執(zhí)行。其他不會(huì)執(zhí)行。
否則:
- 如果有default子句,則執(zhí)行該語(yǔ)句。
- 如果沒有default字句,select將阻塞,直到某個(gè)通信可以運(yùn)行;Go不會(huì)重新對(duì)channel或值進(jìn)行求值。
接口 interface
Go 語(yǔ)言提供了另外一種數(shù)據(jù)類型即接口,它把所有的具有共性的方法定義在一起,任何其他類型只要實(shí)現(xiàn)了這些方法就是實(shí)現(xiàn)了這個(gè)接口。
/* 定義接口 */ type interface_name interface {method_name1 [return_type]method_name2 [return_type]method_name3 [return_type]...method_namen [return_type] }/* 定義結(jié)構(gòu)體 */ type struct_name struct {/* variables */ }/* 實(shí)現(xiàn)接口方法 */ func (struct_name_variable struct_name) method_name1() [return_type] {/* 方法實(shí)現(xiàn) */ } ... func (struct_name_variable struct_name) method_namen() [return_type] {/* 方法實(shí)現(xiàn)*/ }什么時(shí)候需要指針 *
當(dāng)使用 interface 時(shí)不需要 *, 只有 struct 時(shí)才需要 *
type CDog struct { }func (this *CDog) Run() { // 實(shí)現(xiàn) IDog 的所有接口fmt.Println("--- run") }type IDog interface{Run() }var ptr1 interface{} ptr1 = &CDog{} // interface 變量接收的是指針, 而不是對(duì)象 if myDog, ok := ptr1.(*CDog); ok { // 動(dòng)態(tài)匹配 CDog 指針fmt.Printf("--- ptr1 is CDog \n") // --- ptr1 is CDog }if myDog, ok := ptr1.(IDog); ok { // 動(dòng)態(tài)匹配 IDog 接口, 不需要指針?lè)?hào) *fmt.Printf("--- ptr1 is IDog \n") // --- ptr1 is CDog }context
- Go語(yǔ)言實(shí)戰(zhàn)筆記(二十)| Go Context - https://www.flysnow.org/2017/05/12/go-in-action-go-context.html
- https://juejin.im/post/5a6873fef265da3e317e55b6
一個(gè)是Background,主要用于main函數(shù)、初始化以及測(cè)試代碼中,作為Context這個(gè)樹結(jié)構(gòu)的最頂層的Context,也就是根Context。
一個(gè)是TODO,它目前還不知道具體的使用場(chǎng)景,如果我們不知道該使用什么Context的時(shí)候,可以使用這個(gè)。
他們兩個(gè)本質(zhì)上都是emptyCtx結(jié)構(gòu)體類型,是一個(gè)不可取消,沒有設(shè)置截止時(shí)間,沒有攜帶任何值的Context。
-
創(chuàng)建 ctx 的四個(gè)接口
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這四個(gè)With函數(shù),接收的都有一個(gè)partent參數(shù),就是父Context,我們要基于這個(gè)父Context創(chuàng)建出子Context的意思,這種方式可以理解為子Context對(duì)父Context的繼承,也可以理解為基于父Context的衍生。
通過(guò)這些函數(shù),就創(chuàng)建了一顆Context樹,樹的每個(gè)節(jié)點(diǎn)都可以有任意多個(gè)子節(jié)點(diǎn),節(jié)點(diǎn)層級(jí)可以有任意多個(gè)。
-
WithCancel 函數(shù),傳遞一個(gè)父 Context作為參數(shù),返回子Context,以及一個(gè)取消函數(shù)用來(lái)取消Context。 WithDeadline 函數(shù),和 WithCancel 差不多,它會(huì)多傳遞一個(gè)截止時(shí)間參數(shù),意味著到了這個(gè)時(shí)間點(diǎn),會(huì)自動(dòng)取消 Context,當(dāng)然我們也可以不等到這個(gè)時(shí)候,可以提前通過(guò)取消函數(shù)進(jìn)行取消。
-
WithTimeout 和 WithDeadline基本上一樣,一個(gè)是 多少時(shí)間后 超時(shí)自動(dòng)取消,一個(gè)是 什么時(shí)間點(diǎn) 超時(shí)自動(dòng)取消。
-
WithValue 函數(shù)和取消Context無(wú)關(guān),它是為了生成一個(gè)綁定了一個(gè)鍵值對(duì)數(shù)據(jù)的 Context,這個(gè)綁定的數(shù)據(jù)可以通過(guò) Context.Value 方法訪問(wèn)到
-
protobuf 使用
proto3
- https://juejin.im/post/5bb597c2e51d450e6e03e42d
參考: https://blog.csdn.net/wangshubo1989/article/details/72478014
報(bào)錯(cuò): 語(yǔ)法沒有指定
-> 在 test.proto 指定語(yǔ)法
報(bào)錯(cuò): --go_out: protoc-gen-go: 系統(tǒng)找不到指定的文件
-> 需要將 go get -u github.com/golang/protobuf/protoc-gen-go 下載的 protoc-gen-go.exe 所在目錄加入環(huán)境變量
安裝
下載 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 xxx.pb.go
得先安裝 grpc. 參考: grpc
-
執(zhí)行命令所在路徑為 test_grpc
├─test_grpc │ ├─aaa │ ├─greeter_client │ ├─greeter_server │ └─protos // 所有 .proto 文件執(zhí)行命令
protoc --go_out=plugins=grpc:aaa -I .\protos ./protos/helloworld.proto-
-I .\protos : 指定 依賴路徑, 因?yàn)?proto 文件在同一文件夾內(nèi)必須是相同 package (相同模塊), 如果依賴了其他模塊需要指定 -I 參數(shù)指定搜索路徑
- 奇怪的是帶上 -I 參數(shù), 則會(huì)在輸出目錄 aaa 下直接生成 xxx.pb.go, 不帶 -I 參數(shù)則會(huì)生成多一級(jí)目錄, 也就是 aaa/protos/xxx.pb.go
-
.\protos\helloworld.proto : 指定要生成的文件為 protos\helloworld.proto 文件
- 如果該模塊有多個(gè) proto 文件需要轉(zhuǎn)換的話, 可以使用 ./protos/*.proto, 會(huì)匹配生成多個(gè) xxx.pb.go 文件
- 也可以分別制定多個(gè) proto 文件, 用空格隔開. ./protos/aaa.proto ./protos/bbb.proto
-
--go_out=plugins=grpc:aaa : 輸出文件 到 aaa (必須已經(jīng)存在) 目錄下, 格式為 go 語(yǔ)言的 grpc 格式
- 如果生成普通的 xxx.pb.go 的話, 使用 protoc-gen-go, 則為: --go_out=protoc-gen-go:bbb
-
結(jié)果: 會(huì)在 aaa 文件夾下生成 xxx.pb.go 文件
-
生成 普通 xxx.pb.go
-
執(zhí)行命令. ( protoc-gen-go.exe 已經(jīng)在環(huán)境變量中, 且輸出目錄 bbb 已經(jīng)存在 )
protoc -I .\protos\ --go_out=protoc-gen-go:bbb ./protos/helloworld.proto
插件 gofast, 高性能
- golang使用protobuf - https://segmentfault.com/a/1190000009277748
gogoprotobuf有兩個(gè)插件可以使用
- protoc-gen-gogo:和protoc-gen-go生成的文件差不多,性能也幾乎一樣(稍微快一點(diǎn)點(diǎn))
- protoc-gen-gofast:生成的文件更復(fù)雜,性能也更高(快5-7倍)
proto2 語(yǔ)法生成字段都是指針
- https://github.com/golang/protobuf/issues/22
grpc
- grpc應(yīng)用詳解 - https://www.jishuwen.com/d/2BV4
- Go實(shí)踐微服務(wù) – gRPC配置和使用 - https://yuanxuxu.com/2018/06/20/go-microservice-in-action-grpc/
- gRPC 基礎(chǔ): Go - https://doc.oschina.net/grpc?t=60133
- https://github.com/micro/development/blob/master/clients.md
使用的 proto 的語(yǔ)法必須是3. ( syntax = "proto3"; )
安裝
go get -u -v google.golang.org/grpc生成包含 grpc 的 pb
- Go實(shí)戰(zhàn)–golang中使用gRPC和Protobuf實(shí)現(xiàn)高性能api(golang/protobuf、google.golang.org/grpc) - https://blog.csdn.net/wangshubo1989/article/details/78739994
單向調(diào)用
也就是 請(qǐng)求->響應(yīng) 式的調(diào)用.
參考: 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 }雙向調(diào)用
雙方都可主動(dòng)推送流過(guò)去
參考: grpc_stream_putget
// proto syntax = "proto3";//聲明proto的版本 只能 是3,才支持 grpcpackage pro;service Greeter {rpc AllStream (stream StreamReqData) returns (stream StreamResData){} }message StreamReqData {string data = 1; }message StreamResData {string data = 1; }// go //客戶端服務(wù)端 雙向流, req res 都定義了 stream, 必須通過(guò) recv 和 send 接收發(fā)送數(shù)據(jù), 然后再通過(guò) .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 }TLS
- 帶入gRPC:基于 CA 的 TLS 證書認(rèn)證 - https://studygolang.com/articles/15331
模塊依賴 go mod
- 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 ( 建議, 詳細(xì) ) - https://leileiluoluo.com/posts/golang-modules.html
自從 Go 官方從去年推出 1.11 之后,增加新的依賴管理模塊并且更加易于管理項(xiàng)目中所需要的模塊。模塊是存儲(chǔ)在文件樹中的 Go 包的集合,其根目錄中包含 go.mod 文件。 go.mod 文件定義了模塊的模塊路徑,它也是用于根目錄的導(dǎo)入路徑,以及它的依賴性要求。每個(gè)依賴性要求都被寫為模塊路徑和特定語(yǔ)義版本。
從 Go 1.11 開始,Go 允許在 $GOPATH/src 外的任何目錄下使用 go.mod 創(chuàng)建項(xiàng)目。在 $GOPATH/src 中,為了兼容性,Go 命令仍然在舊的 GOPATH 模式下運(yùn)行。從 Go 1.13 開始,模塊模式將成為默認(rèn)模式。
環(huán)境變量設(shè)置
export GO113MODULE=on // 113 是對(duì)應(yīng) go 的版本 1.13 export GOPROXY=https://goproxy.io // 設(shè)置代理 // 可以翻墻的話就不用設(shè)置這個(gè)代理了初始化新模塊.
在 $GOPATH/src 之外的任何地方創(chuàng)建一個(gè)新的目錄
F:\a_link_workspace\go\GoWinEnv_Test01\src (master -> origin) λ mkdir backend && cd backend模塊初始化. 命令: go mod init xxx. xxx 為模塊名 ( 模塊名最好和倉(cāng)庫(kù)根目錄路徑一致 ) . 會(huì)生成一個(gè)模塊配置文件 go.mod
F:\a_link_workspace\go\GoWinEnv_Test01\src\backend (master -> origin) λ go mod init backend go: creating new go.mod: module backend-
查看內(nèi)容
F:\a_link_workspace\go\GoWinEnv_Test01\src\backend (master -> origin) λ cat go.mod module backendgo 1.13
測(cè)試. 新建一個(gè) main.go, 引入一個(gè)第三方模塊
package mainimport "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.gogo.mod 會(huì)自動(dòng)引入的第三方模塊,
module backendgo 1.13require (github.com/gin-gonic/gin v1.4.0github.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 會(huì)格式化 go.mod
-
go mod graph 把模塊之間的依賴圖顯示出來(lái)
-
go mod init 初始化模塊(例如把原本dep管理的依賴關(guān)系轉(zhuǎn)換過(guò)來(lái))
-
go mod tidy 增加缺失的包,移除沒用的包
-
go mod vendor 把依賴拷貝到 vendor/ 目錄下
修改 vendor 第三方庫(kù)使其生效. 參考: 使用本地包
把第三方庫(kù)指到 vendor 目錄下對(duì)應(yīng)的庫(kù)即可
-
go mod verify 確認(rèn)依賴關(guān)系
-
go mod why 解釋為什么需要包和模塊
使用本地包
- go module使用本地包 - https://segmentfault.com/a/1190000018672890
有些情況下需要修改第三方庫(kù)進(jìn)行 調(diào)試/打log, 不引用遠(yuǎn)程包
直接修改 go.mod 文件, 修改 go-plugins 包的指向
require (github.com/micro/go-plugins v1.3.0... )replace github.com/micro/go-plugins => ../github.com/micro/go-plugins // 修改為本地包, 可以使用相對(duì)于 go.mod 的相對(duì)路徑, 也可以使用絕對(duì)路徑然后修改本的 go-plugins 中的代碼就可以編譯進(jìn)去了.
使用 本地包 有個(gè)坑. 因?yàn)?mod 模式下引用的包都是按需下載, 所以下載的第三方庫(kù)并不是所在完整的庫(kù). 一旦代碼引用到這個(gè)庫(kù)里面的其他 package 時(shí), 就會(huì)報(bào)找不到的錯(cuò)誤.
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 一下會(huì)移到 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正確的姿勢(shì)
倉(cāng)庫(kù)根目錄下, 添加一個(gè) 子模塊, 將 完整的包 下下來(lái)丟到 src 目錄下
- 如果不需要提交這個(gè) 子模塊, 在 .gitmodules 中刪掉這個(gè) 子模塊
修改 go.mod 中倉(cāng)庫(kù)的 引用包 的指向
replace github.com/micro/go-micro => ../github.com/micro/go-micro這樣修改 …/github.com/micro/go-micro 目錄下的代碼就能編譯進(jìn)去生效了.
使用其他版本的包
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 )導(dǎo)入所有依賴
- https://www.cnblogs.com/landv/p/10948227.html
使用命令 go get -d -v ./...
- -d : 標(biāo)志只下載代碼包,不執(zhí)行安裝命令;
- -v : 打印詳細(xì)日志和調(diào)試日志。這里加上這個(gè)標(biāo)志會(huì)把每個(gè)下載的包都打印出來(lái);
- ./... : 這個(gè)表示路徑,代表當(dāng)前目錄下所有的文件。
docker go環(huán)境
官網(wǎng)地址: https://hub.docker.com/_/golang/
容器內(nèi)默認(rèn)工作區(qū)是 /go , 所以可以掛載到 /go/src 目錄下
常見問(wèn)題
-
ssh 遠(yuǎn)程連進(jìn)去, 不知道為啥 沒有g(shù)o指令, 需要自己添加到環(huán)境變量中 .bash_profile
export 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
增加環(huán)境變量
# vi ~/.bash_profile ... export GOROOT=/usr/local/go export GOPATH=/mytemp/GoLab # 項(xiàng)目地址 export PATH=$PATH:$GOPATH:/usr/local/go/bin# source ~/.bash_profile # 使其生效查看命名, ok
# go version go version go1.10.3 linux/amd64- 參考: https://blog.csdn.net/tao_627/article/details/79375950
唯一ID生成
- 分布式系統(tǒng)唯一ID生成方案匯總 - https://www.cnblogs.com/wyb628/p/7189772.html
RabbitMQ
- 我為什么要選擇RabbitMQ ,RabbitMQ簡(jiǎn)介,各種MQ選型對(duì)比 - https://www.sojson.com/blog/48.html
反射 reflect
- Go Reflect 性能 - https://colobu.com/2019/01/29/go-reflect-performance/
優(yōu)化, 可以通過(guò) 自定義的生成器腳本, 生成, 避免使用反射
Builder & Option 設(shè)計(jì)模式
- 實(shí)例淺談利用Golang的Builder&Option設(shè)計(jì)模式來(lái)傳遞初始化參數(shù) - 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
GC
- 深入理解Go-垃圾回收機(jī)制 - https://juejin.im/post/5d78b3276fb9a06b1829e691
常見編譯報(bào)錯(cuò)
-
Q: can’t load package: package test: found packages main (base.go) and testgo (test_go.go) in E:\GoLab\src\test
A: 同一個(gè)目錄下不能存在不同包名的文件
declared and not used
聲明但未被使用, 可以這樣屏蔽報(bào)錯(cuò)
xf := xiaofang{} _ = xf踩坑
保存自動(dòng)格式化, 源代碼被自動(dòng)刪除
參考:
https://github.com/microsoft/vscode-go/issues/2604
https://stackoverflow.com/questions/48124565/why-does-vscode-delete-golang-source-on-save
把格式化工具由 goreturns 換成 goformat, 同時(shí)取消掉保存自動(dòng)格式化
"go.formatTool": "gofmt", // 使用 gofmt 工具格式化 "go.alternateTools": {"go-langserver": "gopls" },"[go]": {"editor.formatOnSave": false},go get 報(bào)錯(cuò): ‘xxx’ is not using a known version control system
可能有兩個(gè)原因
go run 報(bào)錯(cuò): The system cannot find the path specified.
可能 go.mod 把某個(gè)包指向了本地 vendor 里面的包, 而 vendor 里面又沒有這個(gè)包.
解決辦法參考: 使用本地包
報(bào)錯(cuò): all goroutines are asleep - deadlock!
main goroutine 在等一個(gè)永遠(yuǎn)不會(huì)來(lái)的數(shù)據(jù),那整個(gè)程序就永遠(yuǎn)等下去了, 這個(gè)時(shí)候就會(huì)報(bào)上述錯(cuò)誤
需要用 sync.WaitGroup 來(lái)保證程序正常退出. 參考: test_error.go
參考:
https://stackoverflow.com/questions/26927479/go-language-fatal-error-all-goroutines-are-asleep-deadlock
https://cloud.tencent.com/developer/article/1418106
map 多線程訪問(wèn)加鎖
即使是只是訪問(wèn), 不做 add 或 delete 操作, 都得加鎖, 不然會(huì)有潛在的錯(cuò)誤:
runtime.mapaccess2_fast64(0x77e360, 0xc0000da000, 0xab1, 0x86f1e0, 0xc001325040)多線程訪問(wèn)的正確姿勢(shì) - 加鎖
a.callMu.Lock() if ci, ok := a.callMap[pbData.Header.ReqID]; ok {ci.ch <- pbData } a.callMu.Unlock()channel 導(dǎo)致死鎖問(wèn)題
等問(wèn)題, 要不是 chan 沒被消費(fèi), 就是被過(guò)度消費(fèi). 例如:
// https://juejin.im/post/5ca318e651882543db10d4ce // 測(cè)試死鎖 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 */總結(jié)