Go 語言是使用包(package)作為基本單元來組織源碼的, Go 程序就是這些包連結起來而構建的。與C 語言的頭文件包含機制相比則是「先進」了許多。
即便是每次編譯都是從頭開始。避免了 C 語言那種通過頭文件分析依賴的巨大開銷。Go 語言以包為基本構建單元的構建模型,依賴分析非常簡單。
一 構建過程
Go 編譯速度快從三個方面分析:
1.Go 要求每個源文件在開頭處顯式地import所有依賴的包,Go編譯器不必讀取和處理整個文件就可以確定其依賴的包列表;
2.Go 要求包之間不能存在循環依賴。由於無環,包可以被單獨編譯,也可以並行編譯;
3.已編譯的 Go 包對應的目標文件(xxx.o 或 xxx.a)中,如下
1) 該包本身的導出符號信息。
2) 還記錄了其所依賴包的導出符號信息。
這樣,Go編譯器在編譯某包M時,針對M依賴的每個包導入(比如:導入包N),只需讀取一個目標文件即可(比如:N包編譯成的目標文件,該目標文件中已經包含了N包的依賴包的導出信息),而無需再讀取其他文件中的信息了。
通過 package 關鍵字聲明 Go 源文件所屬的包:
上述源碼表示:文件 xx.go 是包 x 的一部分。
使用 import 關鍵字導入依賴的標準庫包或第三方包:
import ( "fmt" // 標準庫包導入 「x/y/z" // 第三方包導入 )
func main() { z.FuncName() fmt.Println("Go!Go!Go!") }看到上面代碼都會想到將import後面的」z」、「fmt」與z.FuncName()和fmt.Println()中的z和fmt認同一個語法元素:包名。 但是以後深入學習Go語言後,發現並非這樣。比如實施分布式消息框架nsq提供的官方client包時,包導入如下:
import 「github.com/nsqio/go-nsq」但是使用導出函數的時候,我們不是go-nsq.FuncName(),而是nsq.FuncName:
consumer,_:=nsq.NewConsumer("write_order", "ch", config)
你可能會問最後一個分段到底代表什麼?是包名稱?是一個路徑?
Go程序構建過程和其他主流靜態編譯語言一樣,Go語言的程序構建簡單說是由編譯(compile)和連結(link)兩個階段。
一個非main包在編譯後會對應生成一個.a文件,該文件可以理解為是Go包的目標文件(是通過 pack 工具($GOROOT/pkg/tool/darwin_amd64/pack)對.o文件打包後形成的.a).默認情況下在編譯過程中.a文件生成臨時目錄下,除非使用go install安裝到$GOPATH/pkg下(Go1.11版本前),否則你看不到.a文件。如果構建可執行程序,那麼.a文件會在構建可執行程序的連結階段起使用。
標準庫包的源碼文件在$GOROOT/src 下面,而對應的 .a 文件存放在$GOROOT/pkg/darwin_amd64 下(以 MacOS 上為例;如果是 linux,則是 linux_amd64)
那麼構建Go程序時,編譯器會重新編譯依賴包的源文件還是直接連結包的.a文件呢?
在使用第三方包的時候,當第三方包原始碼存在且對應的.a已安裝的情況下,編譯器連結的仍是根據第三方包最新原始碼編譯出來的.a文件,而不是之前已經安裝到
$GOPATH/pkg/darwin_amd64 下面的目標文件。
那Go 標準庫中的包也是這樣的嗎?
默認情況下對於標準庫中的包,編譯器直接連結的是$GOROOT/pkg/darwin_amd64下的.a文件。
二 路徑名?包名?
通過上面的知識,知道了編譯器在編譯過程中必然要使用的是編譯單元(包)所依賴的包的源碼。而編譯器要找到依賴包的源碼文件就需要知道依賴包的源碼路徑。這個路徑由兩部分組成:
1.基礎搜索路徑
2.包導入路徑
基礎搜索包是一個全局的設置,下面介紹一下:
所有包(標準庫還是第三方包)的源碼基礎搜索路徑都包括 $GOROOT/src
在上述基礎搜索路徑的基礎上,不同版本的基礎搜索路徑有不同:
1.11 之前
$GOPATH/src
1.11-1.12 有三種:
經典gopath模式,GO111MODULE=off , $GOPATH/src
module-aware 模式,GO111MODULE=on, $GOPATH/pkg/mod
auto 模式,GO111MODULE=auto,在$GOPATH/src 路徑下,與 gopath 模式相同;在$GOPATH/src 路徑外且包含 go.mod,與 module-aware 模式相同。
1.13 有兩種
經典gopath 模式,GO111MODULE=off,$GOPATH/src
module-aware 模式下,GO111MODULE=on/auto,$GOPATH/pkg/mod;
1.13後
只有 module-aware 模式,即只在 module 緩存的目錄下搜索包的源碼。
搜索路徑第二部分,是位於每個包源碼文件頭部的包導入路徑。基礎搜索路徑與包導入路徑結合在一起,Go編譯器就可以確定一個包的所有依賴包的源碼路徑的集合,這樣集合構成了Go編譯器的源碼搜索路徑空間。
三 同一源碼的依賴包在同一源碼搜索路徑下包名衝突,怎麼辦?
package main
import ( "github.com/xxx/p1/pkg/pkg11" 「github.com/xxx/p2/pkg/pkg11" )
func main() { pkg1.Func1() }當你有這樣的import的時候,運行main,直接報錯。會告訴你包衝突。如何解決呢?
package main
import ( p1 "github.com/xxx/p1/pkg/pkg11" p2 「github.com/xxx/p2/pkg/pkg11")
func main() { p1.Func1() p2.Func1()}這樣就ok了,解決了問題。