關於go語言的那點事

2023-07-29 06:00:45

本篇是語言討論的「傳統專案」。每個寫go語言討論的人,都會介紹它的發展歷程,應用領域,優缺點和特點來介紹go語言的那點事,當然這點事只是我從我的視角來看的。當然如果你有自己對go語言的看法,那就更好了,歡迎討論!

「經典」簡介

go語言由google公司開源的用於提高程式設計師程式設計效率的程式語言。它是一門簡單易學,靜態編譯,原生並行以及向後相容性的高效快捷的輕量級語言。

發展歷程

目前go語言每半年會迭代一個新版本,從2012年的go1.0開始,已經歷經21個版本,目前已經到了go1.20,從2015年開始進入快速發展期,以用於大型專案的工程語言進入人們的視野。

go語言在go1.1-g1.4時還不具備工程化的條件,但在go1.5時開始具備工程化的條件,這是因為(1)在go1.5之前的版本golang採用的是c語言編譯器,(2)gc的STW時間會很長,(3)第三方包沒有合理的存放位置。

而在go1.5版本開始實現go語言自舉,在這個版本里開始採用三色標記法,這使得golang的gc時間大幅下降,經過go1.6-go1.7的改進,使得golang的STW時間大幅縮短,另外在這個版本開始實驗vender機制。

這三個特性的加入使得golang在2015年之後開始火爆的原因,在go1.7正式使用vender機制,在go1.9版本STW時間縮短至100us以內,為此golang在go1.9版本已經完全具備工程化的條件。

之後golang在go1.11版本開始實驗go mod機制以替換vender機制,在go1.13正式引入go mod機制,徹底解決了第三方庫的版本問題,在go1.18版本中實驗golang的泛型特性,而在go1.20版本中正式引入golang的泛型特性。


應用場景

go語言用於雲原生開發,命令列介面,網站開發,運維開發領域以及儲存開發。注意我這裡介紹go語言的應用場景來自go語言官網實際上,你會發現go語言還能應用於其他領域,如數位貨幣(以太坊),物聯網等等(因為我不熟悉這些領域,所以我可以假裝不知道嗎?)

雲和網路服務

隨著docker和kubernetes等殺手級應用的出現和發展,golang逐漸成為雲和網路服務領域中舉足輕重的語言,目前由超過75%的雲原生計算基金專案都是使用go語言開發的。


命令列介面

對於命令列介面應用,go語言能夠將其快速地構建成二進位制程式,提供跨平臺工作的開發方式以及強大的社群支援。我們可以在windows或者mac上開發或者偵錯,在Linux上編譯部署。它通過靜態編譯構建的二進位制程式隨插即用,幾乎不需要任何其他依賴。


例如,hub是github推出的用於輔助git的命令列工具,它是用go語言編寫的。另外如greenplum的gpbackup和gprestore也是用go語言編寫的。


網站

go語言不僅天生支援高並行,能以極小的開銷約4K的記憶體啟動一個攜程goroutine,而且go語言的標準庫對於web開發的強大支援,能讓業務人員專注於業務開發,這些使得go語言在網站開發方面佔有一席之地。

注意go語言其實已經有gin以及beego這樣成熟的web框架,甚至有人已經將gin,vue3結合起來gin-vue-admin這樣的專案。我的建議是如果你寫的web服務很簡單,那麼go語言標準庫基本已經夠用,否則請選擇上述框架。


運維

go語言在運維開發編寫指令碼時擁有大量優秀的標準庫支援。另外,go語言還提供了豐富的工具鏈幫助用於編寫高效、健壯以及可維護性強的程式。


目前在運維開發領域,使用go語言開發優秀的專案有類似於zabbix的Prometheus,將運維資料視覺化的grafana,用於容器CI/CD的Docker CI/CD等等


儲存

go語言是帶有gc,高效能並且高並行的特點,為此在儲存領域也有比較好的發展。例如分散式資料庫tidb(sql層使用go語言開發)、時序性資料庫influxdb、分散式kv儲存etcd以及分散式訊息佇列nsq都是其中的佼佼者。


優點

簡單易學

sum := 0
for i := 0; i < 30; i ++ {
    if i % 2 == 0 {
        sum += i
    }
}
fmt.Println(sum)

go語言許多語法和C語言很相似,學習難度和python類似,學習成本較低,大約1-2周初學者就能開發一些實用的小程式。當然如果你學過C語言,你將更快入門go語言


隨插即用

go語言採用靜態編譯的方式,產生出的二進位制程式隨插即用,幾乎無需其他依賴。

原生並行

c := make(chan int)
go func() {
    for {
        select {
        case e, ok := <-c:
            if !ok {
                return
            }
            fmt.Println(e)   
        }
    } 
}()

a := []int{5,2,4,3,1}
for _, v := range a {
    c <- v
}
close(c)

go語言可以很容易地實現並行程式設計,通過保留字go即可實現,使用chan實現攜程間通訊,你可以認為chan是一個加鎖的佇列或者訊息管道。

如上,程式碼通過保留字go呼叫匿名函數啟動了一個攜程,並且通過chan向這個攜程傳值。

注意:chan的傳入並非是全域性變數而是因為go語言匿名函數的閉包特性。


社群支援

go語言雖然歷經時間不多,但是它的社群支援極為強大,如上go語言目前支援常用資料庫的存取。

向後相容性承諾

使用go語言編寫的程式碼會被其之後版本的go語言相容,例如你用go語言1.8寫了程式碼,那麼幾乎不用做出任何更改在go語言1.17中直接編譯使用。這就意味著通過這個承諾,你幾乎無需修改程式碼就能讓go語言程式碼享受新版本帶來的效能提升。

強大的標準庫以及豐富的工具鏈

優秀的編譯執行速度

go語言不僅有著c/c++,java等傳統編譯語言無法企及的編譯速度,又有類似於c/c++的執行速度,這些讓程式設計師有更好的程式設計體驗。當然據說這是Google公司的陰謀,讓程式設計師有「更多時間工作」。


缺點

  • go語言對GUI使用者介面支援不夠完善,需要額外的qt庫支援或者採用wasm技術。
  • go語言對機器學習方面支援不夠完善,存在神經網路和go語言對應庫對接成本。
  • go語言不支援aix7.2以下的aix作業系統,這是由於go語言底層有少量通過組合寫成,對硬體有部分要求。
  • go語言對行動端app開發支援不夠完善,在api使用上存在限制。
  • gol語言缺乏對傳統異常捕獲機制的支援,需要通過大量的if語句去判定其是否有有異常。

特點

這是相較於c/c++ ,java,javascript以及python等主流語言不一樣的地方,不算是優點也不算是缺點的語言特性


多返回值

由於基本上目前的主流語言的函數都是單返回值,而golang為了使錯誤能夠被返回,採用了含有多返回值的函數,如下:

func Write(data []byte) (n int, err error)

還有表示式中也可以使用多返回值,例如交換a,b可以寫成

a:= 1
b:= 2
a,b = b,a

還有前面提到的

for _, v := range a {
    c <- v
}

以及

select {
    case e, ok := <-c:
    if !ok {
        return
    }
    fmt.Println(e)   
}

公有私有

golang並非通過指定關鍵字private和public去實現物件導向的封裝特性,而是通過大小寫去指定成員是否公有和私有,在下述程式碼中,writer包外的只能存取公有的成員變數,函數,或者方法。

package writer
import (
    "os"
)

var (
    OsType = "Linux"  //公有變數
    osType = "linux"  //私有變數
)

type FileWriter struct {
	Filename string //共有成員變數
    f * os.file     //私有成員變數
}

//公有函數
func NewFileWriter(filename string) (*FileWriter,  error){ 
    return newFileWriter(filename)
}
//私有函數
func newFileWriter(filename string) (fw *FileWriter, err error){
    fw = &FileWriter{
        Filename:filename,
    }
   	err = fw.createFile()
    return
}

//公有方法
func (w *FileWriter) Write(data []byte) (n int, err error){
   	return w.f.Write(data)
}

//公有方法
func (w *FileWriter) Close() (err error){
    return w.f.Close()
}

//私有方法
func (w *FileWriter) createFile() (err error){
    w.f, err = os.Create(w.Filename)
    if err != nil{
        return err
    }
    return
}

面向介面

golang由於採用了靜態編譯的方式,使得golang無法在執行時載入,為此它使用組合而沒有繼承,使用面向介面來實現物件導向的多型特性,這樣更容易寫出高內聚低耦合的程式碼。

例如我們定義一個介面

type Writer interface {
    Write(data []byte) (n int, err error)
}

然後上述程式碼中的FileWriter已經實現該介面

type FileWriter struct {
	filename string
    f * os.file
}

func (w *FileWriter) Write(data []byte) (n int, err error){
   	return w.f.Write(data)
}

網路同步程式設計

網路程式設計往往如下圖採用非同步回撥的方式去完成事件處理。

但是在golang中網路程式設計可以使用同步模式去完成,這可能不是一個很好的例子。


func HandleConn(conn net.Conn) {
    defer conn.Close()
    for {
        buf := make([]byte, 1024)
		cnt, err := conn.Read(buf)
		if err != nil {
		    //請處理錯誤
	    }
        //處理資訊
    }
}

func main() {
    listen, err := net.Listen("tcp", ":8888")
    if err != nil {
        //處理錯誤
        return
    }

    for {
        conn, err := listen.Accept()
        if err != nil {
            //處理錯誤
            return
        }

        go HandleConn(conn)
    }
}