01 Get started

Go 語言簡介

圖片來源:Gophers

Go 於 2007 年誕生,由 Google 創建。

Go 程式語言沒有以下語法或機制:

  • 沒有型別繼承。
  • 沒有 exception handling 語法(沒有 try...catch,也沒有 throw)。
  • 沒有巨集(macro)。
  • 沒有 enum。(可以用具名常數)
  • 沒有局部函式(partial functions)。
  • 不支援變數延遲估值(lazy variable evaluation)。
  • 沒有運算子多載(operator overloading)。
  • 沒有樣式匹配(pattern matching)。
  • 沒有內建的 GUI 框架或套件。

如欲了解為什麼 Go 不支援某些語言特性,可參閱官方文件:Go FAQ

Go 的優點與強項:

  • 很適合開發 CLI 和伺服器端應用程式。
  • 函式可回傳多個值。於是,函式可以輕易回傳錯誤,故也就不需要 throw exceptions 了。就如 Rob Pike 於 2015 年發表的文章所說,errors are values。直到現在(2024 年)依然如此。
  • Concurrency。非同步呼叫的語法非常簡單直觀,跟循序呼叫的語法幾乎一樣。
  • 單元測試在 Go 語言中是一級公民:測試程式的檔案名稱一律命名為「欲測試之程式檔名_test.go」,而且兩個檔案要放在同一個目錄下。例如 hello.go 的測試程式會叫做 hello_test.go。
  • 標準函式庫提供了常用的工具套件,包括網路通訊、HTTP、序列化、加解密等等。

如果需要開發跨平台的 GUI 應用程式,可以試試開源專案 Wails

Go Runtime

每一個可執行的 Go 應用程式的內部結構大致如下圖:

Go Runtime

除了應用程式本身的機器碼和它依賴的外部套件(dependencies),還會包含一個叫做 Go Runtime 的東西,用來管理應用程式執行時的行為:

  • Go Scheduler: 管理併發的(concurrent)函式,即所謂的 goroutines
  • Garbage Collector: 簡稱 GC,它會監看應用程式的記憶體使用情形,自動將沒有用到的記憶體回收。

撰寫 Go 程式的時候不用擔心何時該回收記憶體,這都要歸功於 GC 在背後提供的服務。

如欲了解 GC 的運作細節,可參考官方文件:A Guide to the Go Garbage Collector

Goroutines

Goroutines 是獨立執行的併發函式(concurrent functions)。

concurrent vs. parallel

如果說「併行」或「並行」,很容易令人迷惑到底所指為何,故我選擇把 concurrent 翻譯為 「併發」,parallel 則為「平行」,以便容易區別。明確起見,有時甚至只寫英文,例如 concurrency(中文也許可用「併發能力」,但還是覺得英文最不易令人混淆)。

Concurrency 是 Go 語言在設計之初就提供的特性,而不是後來想到才加入的。Go 的併發函式有一個專屬名稱:goroutine,其寫法相當直觀且簡單,跟循序執行的函式沒有太大差別。舉例來說,如果你有一個函式叫做 parseFile()

func parseFile(filename string) {
  ...
}

如果只需要循序執行,呼叫該函式的寫法為:

parseFile("file1.dat")
parseFile("file2.dat")

若需要併發執行,則呼叫的時候加上關鍵字 go 即可,像這樣:

go parseFile("file1.dat")
go parseFile("file2.dat")

其美妙之處在於,無論是循序還是併發執行,改變的地方只有呼叫該函式的寫法,而函式本身的宣告完全不需要改動。

Note

有的程式語言在撰寫併發函式的時候,會要求必須在函式宣告的地方加上額外的關鍵字(例如 async),代表該函式必須以併發的方式呼叫。換言之,一旦函式宣告為併發函式,那麼它的上游(呼叫端)也必須是併發函式,如此一路沿著呼叫路徑往上層蔓延開來。

Go Scheduler

當一個應用程式執行起來的時候,是由作業系統將它載入至記憶體中,這個載入記憶體中運行的應用程式叫做 process,中文譯為「行程」或「處理序」。每一個 process 裡面有一條執行的主線(可簡化理解為主流程),即所謂的 main thread,而 thread 就是每個 process 當中最小的執行單元,中文譯為「執行緒」或「線程」。除了 main thread 之外,一個 process 裡面可以有其他 threads,經常稱為 worker threads。如果機器本身有多個 CPU 核心,便可能讓一個具有多執行緒的應用程式同時執行多項工作,從而提升效能或改善回應品質(responsiveness)。

在多執行緒的情況下,作業系統會視需要進行執行緒切換,也就是讓 CPU 在不同的執行緒之間切來切去,這個過程叫做 context switching。這個切換執行緒的操作會有不少成本,因為 CPU 必須先保存當前執行緒的狀態資料,然後載入下一個執行緒的狀態並執行它。在僧多粥少的情況下,眾多執行緒都需要少數幾個 CPU 提供服務,便會導致大量且頻繁的執行緒切換,因而降低應用程式的效能。

為了減少執行緒切換的成本,並簡化多執行緒應用程式設計的複雜性,Go 提出了以下對策:

  • 將作業系統的執行緒簡化(抽象化)為 goroutines。
  • 提供 goroutine 專用的排程器(scheduler)來簡化作業系統處理執行緒的排程工作。

如下圖所示,Go 排程器的主要工作就是把 goroutines 分派給特定的執行緒。應用程式運行時,可能有成千上萬個 goroutines 在排隊等待自己被分派到一個執行緒。

在 Go 應用程式中,goroutines 之間的 context switching 成本比作業系統層級的執行緒切換成本更低,因為 Go Scheduler 是屬於應用程式層級(另一種常見說法是 user space)的處理,而未涉及作業系統核心層級(kernel space)的低階工作。

當一個 goroutine 被阻斷而暫停工作時(例如可能正在等待某個磁碟或網路 I/O 操作),Go Schedular 就會暫時把它晾在一邊,並將它占用的執行緒分派去執行另一個需要執行的 goroutine。如此一來,同一個執行緒便得以重複使用,服務多個 goroutines。此外,一個 goroutine 占用的記憶體空間相當節省:剛開始只需要配置 2KB 的堆疊記憶空間,之後則會根據實際需要增加或縮減。

Information

這裡的介紹比較簡略,若有興趣深入了解 Go Scheduler 的內部工作原理,可參考這個影片:GopherCon 2018: The Scheduler Saga

Channels

(TODO)

建立開發環境

安裝 Go

請參閱官方文件:Download and install

安裝完成後,開啟命令列視窗,執行 go version 命令查看版本。

撰寫本文時,我安裝的 Go 版本是 v1.23.0。

VS Code

比較常聽到建議使用的 IDE:

  • Visual Studio Code
  • GoLand by JetBrains
  • Neovim

對鍵盤操控和 coding 效率極為講究的人可能會喜歡 Neovim 或 JetBrains。由於我用的是 VS Code,所以這裡只介紹它的相關設定。

與 Go 有關的 VS Code extensions:

安裝 Go tools

安裝好 Go for VS Code 之後,接著安裝 Go 工具鍊。步驟為:按 Ctrl+Shift+P 或 F1 開啟 Command Palette,然後在搜尋框輸入 Go: Install/Update tools,接著會出現下拉清單顯示建議安裝的 Go tools,全選之後按 OK 即可。參考下圖:

左下角的 Go 面板可以查看 Go 環境變數以及安裝了哪些 Go tools。

亦可參閱:Install and configure Visual Studio Code for Go development

如果已經有正確安裝 Go 工具鍊的相關工具,在預設情況下,按 Ctrl+S 存檔時會自動重新排版程式碼,可輕鬆維持一致的程式碼風格。如欲查看預設的自動排版選項,可以按 F1Crtl+Shift+P 開啟命令面板,輸入 Preferences: Open Default Settings (JSON),便可以找到所有跟 Go 有關的預設選項。底下僅摘錄其中一部份:

  // Configure settings to be overridden for the go language.
  "[go]":  {
    "editor.insertSpaces": false,
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.organizeImports": true
    }
  },

  // Enable intellisense, code navigation, refactoring, formatting & diagnostics
  // for Go. The features are powered by the Go language server "gopls".
  "go.useLanguageServer": true,

其中提到的 "gopls"(讀作 "go please")是官方提供的、用於 VS Code 的 Language Server。只要有安裝 gopls,在 VS Code 中撰寫程式就會有許多方便的編輯功能,像是 intellisense、重構、排版程式碼等等。

參閱: gopls 官方文件

順便提及,Go 提供的程式碼排版工具預設會使用 tab 來縮排,而不是插入空白字元,故剛才展示的預設選項中,editor.insertSpace 預設為 false。建議不要更改這個選項,以確保所有的 Go 程式碼維持同樣的風格。

除錯

欲在 VS Code 中除錯 Go 程式,通常需要建立 launch.json 來提供一些必要的參數。

舉例來說,如果命令列應用程式執行的過程中有用到 fmt.Scanf() 來獲取使用者輸入的字元,在預設情況下,VS Code 除錯應用程式的時候是以整合式終端機視窗(integrated terminal)來顯示應用程式的執行過程,而這個整合式終端機並沒有辦法接受使用者輸入的字元。像這種情況,就會需要告訴 VS Code:除錯我的應用程式時,請改用外部的終端機視窗(external terminal)。底下是一個 launch.json 範例:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch Package",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${fileDirname}",
            "console": "externalTerminal"
        }
    ]
}

有關建立 launch.json 的方法以及詳細的參數說明,請參閱 Go Wiki : debugging

VS Code 其他設定

我覺得 VS Code 預設的 Explorer view 在顯示資料夾和檔案的樹狀階層時,內縮的距離太小,以至於階層區分不明顯。欲修改預設值,可按 F1 或 Ctrl+Shift+P 開啟 Command Palette,然後在搜尋框輸入 Preferences: Open User Settings,接著在 Settings 的搜尋框輸入 Workbench tree indent,便可找到對應的選項。我通常將此選項的數值設定成 14 或 16(單位是 pixels)。

References


先這樣,也許有空時會再更新。   我的其他站點:      

Last modified: 2024-10-10