Go 語言編程 — Overview

目錄

緣起

Golang 始於 2007 年,2009 年 11 月正式開源,2012 發佈了 Go 1 穩定版本。

創始人:

  • Ken Thompson(肯·湯普森):貝爾實驗室 Unix 團隊成員,C 語言、Unix 和 Plan 9 的創始人之一。
  • Rob Pike:Golang 項目總負責人,貝爾實驗室 Unix 團隊成員,合著了《The Unix Programming Environment》,對 UNIX 的設計理念做了正統的闡述。
  • Robert Griesemer:就職於 Google,負責 Chrome 瀏覽器和 Node.js 使用的 Google V8 JavaScript 引擎的代碼生成部分,對語言設計有深入的認識。

後來還加入了 Ian Lance Taylor、Russ Cox 等人。這些計算機科學領城的重量級人物設計 Golang 的初衷是滿足 Google 的需求:在不損失應用程序性能的情況下降低代碼的複雜性,具有 “部署簡單、併發性好、語言設計良好、執行性能好” 等優勢。

計算機軟件經歷了數十年的發展,形成了多種學術流派,例如:面向過程編程、面向對象編程、函數式編程、面向消息編程等。此外,近年來也出現了一些小衆的編程哲學。

Golang 對這些思想亦有所吸收。例如:Golang 接受了函數式編程的一些想法,支持匿名函數與閉包;Golang 接受了以 Erlang 語言爲代表的面向消息編程思想,支持 Goroutine 和 Channel,並推薦使用消息而不是共享內存來進行併發編程。

在 Golang 出現之前,開發者們總是面臨非常艱難的抉擇,究竟是使用執行速度快但是編譯速度並不理想的語言(如:C++),還是使用編譯速度較快但執行效率不佳的語言(如:.NET、Java),或者說開發難度較低但執行速度一般的動態語言(如:Python)呢?

在 Google I/O 2012 的 Go 設計小組見面會上,Rob Pike 是這樣說的:“我們做了大量的 C++ 開發,厭煩了等待編譯完成,儘管這是玩笑,但在很大程度上來說也是事實。”

顯然,Go 語言在這 3 個條件之間做到了最佳的平衡:快速編譯,高效執行,易於開發

Golang 的吉祥物

Go Gopher,這是才華橫溢的插畫家 Renee French 設計的,她也是 Go 設計者之一 Rob Pike 的妻子。
在這裏插入圖片描述

Golang 的特性

語法簡單

Golang 的語法規則嚴謹,沒有歧義,更沒什麼黑魔法變異用法。任何人寫出的代碼都基本一致,這使得 Golang 簡單易學。放棄部分 “靈活” 和 “自由”,換來更好的維護性。

將 ++ 、-- 從運算符降級爲語句,雖然保留了指針,但默認阻止指針運算。將切片和字典作爲內置類型,從運行時的層面進行優化,這也算是一種 “簡單”。

原生支持併發編程

Golang 是在多核和網絡化的時代背景下誕生的原生支持併發的編程語言。Golang 從底層原生支持併發,無須第三方庫,開發人員可以很輕鬆地在編寫程序時決定怎麼使用 CPU 資源。

Golang 的併發基於 Goroutine,Goroutine 類似於線程,但並非線程。可以將 Goroutine 理解爲一種虛擬線程。Golang 運行時會參與調度 Goroutine,並將 Goroutine 合理地分配到每個 CPU 中,最大限度地使用 CPU 性能。

可以說,Goroutine 是 Go 最顯著的特徵。它用 “類協程” 的方式來處理併發單元,卻又在運行時層面做了更深度的優化處理。這使得語法上的併發編程變得極爲容易,無須處理回調,無須關注線程切換,僅需使用一個關鍵字,簡單而自然。

多個 Goroutine 中,Golang 使用 Channel(通道)進行通信,通道是一種內置的數據結構,可以讓用戶在不同的 Goroutine 之間同步發送具有類型的消息。這讓編程模型更傾向於在 Goroutine 之間發送消息,而不是讓多個 Goroutine 爭奪同一個數據的使用權。

Goroutine 搭配 Channel,實現 CSP 模型。將併發單元間的數據耦合拆解開來,各司其職,這對所有糾結於內存共享、鎖粒度的開發人員都是一個可期盼的解脫。若說有所不足,那就是應該有個更大的計劃,將通信從進程內拓展到進程外,實現真正意義上的分佈式。

程序可以將需要併發的環節設計爲生產者模式和消費者的模式,將數據放入通道。通道另外一端的代碼將這些數據進行併發計算並返回結果,如下圖所示:

在這裏插入圖片描述

示例:生產者每秒生成一個字符串,並通過通道傳給消費者,生產者使用兩個 Goroutine 併發運行,消費者在 main() 函數的 Goroutine 中進行處理。

package main

import (
        "fmt"
        "math/rand"
        "time"
)

// 數據生產者
func producer(header string, channel chan<- string) {

     // 無限循環, 不停地生產數據
     for {

            // 將隨機數和字符串格式化爲字符串發送給通道
            channel <- fmt.Sprintf("%s: %v", header, rand.Int31())

            // 等待1秒
            time.Sleep(time.Second)
        }
}

// 數據消費者
func customer(channel <-chan string) {

     // 不停地獲取數據
     for {

            // 從通道中取出數據, 此處會阻塞直到信道中返回數據
            message := <-channel

            // 打印數據
            fmt.Println(message)
        }
}

func main() {

    // 創建一個字符串類型的通道
    channel := make(chan string)

    // 創建producer()函數的併發goroutine
    go producer("cat", channel)
    go producer("dog", channel)

    // 數據消費函數
    customer(channel)
}

運行結果:

dog: 2019727887
cat: 1298498081
dog: 939984059
cat: 1427131847
cat: 911902081
dog: 1474941318
dog: 140954425
cat: 336122540
cat: 208240456
dog: 646203300

整段代碼中,沒有線程創建,沒有線程池也沒有加鎖,僅僅通過關鍵字 “go” 實現 goroutine,和 channel 實現數據交換。

內存分配

Go 選擇了 tcmalloc 內存分配器,它本就是爲併發而設計的高性能內存分配組件。

可以說,內存分配器是運行時三大組件裏變化最少的部分。刨去因配合垃圾回收器而修改的內容,內存分配器完整保留了 tcmalloc 的原始架構。使用 cache 爲當前執行線程提供無鎖分配,多個 central 在不同線程間平衡內存單元複用。在更高層次裏,heap 則管理着大塊內存,用以切分成不同等級的複用內存塊。快速分配和二級內存平衡機制,讓內存分配器能優秀地完成高壓力下的內存管理任務。

在最近幾個版本中,編譯器優化卓有成效。它會竭力將對象分配在棧上,以降低垃圾回收壓力,減少管理消耗,提升執行性能。可以說,除偶爾因性能問題而被迫採用對象池和自主內存管理外,我們基本無須參與內存管理操作。

自動垃圾回收

在垃圾回收方面,Go 面臨了很多困難。因指針的存在,所以回收內存不能做收縮處理。幸好,指針運算被阻止,否則要做到精確回收都難。

每次升級,垃圾回收器必然是核心組件裏修改最多的部分。從併發清理,到降低 STW 時間,直到 Go 的 1.5 版本實現併發標記,逐步引入三色標記和寫屏障等等,都是爲了能讓垃圾回收在不影響用戶邏輯的情況下更好地工作。儘管有了努力,當前版本的垃圾回收算法也只能說堪用,離好用尚有不少距離。

使用靜態鏈接

靜態編譯的好處顯而易見。將運行時、依賴庫直接打包到可執行文件內部,簡化了部署和發佈操作,無須事先安裝運行環境和下載諸多第三方庫。這種簡單方式對於編寫系統軟件有着極大好處,因爲庫依賴一直都是個麻煩。

支持交叉編譯

Golang 支持交叉編譯,可以在 Linux 操作系統上開發運行於 Windows 的應用程序。

國際化

這是第一門完全支持 UTF-8 的編程語言,這不僅體現在它可以處理使用 UTF-8 編碼的字符串,就連它的源碼文件格式都是使用的 UTF-8 編碼。Go 語言做到了真正的國際化!

標準庫

功能完善、質量可靠的標準庫爲編程語言提供了充足動力。在不借助第三方擴展的情況下,就可完成大部分基礎功能開發,這大大降低了學習和使用成本。最關鍵的是,標準庫有升級和修復保障,還能從運行時獲得深層次優化的便利,這是第三方庫所不具備的。

Go 標準庫雖稱不得完全覆蓋,但也算極爲豐富。其中值得稱道的是 net/http,僅須簡單幾條語句就能實現一個高性能 Web Server,這從來都是宣傳的亮點。更何況大批基於此的優秀第三方 Framework 更是將 Go 推到 Web/Microservice 開發標準之一的位置。

當然,優秀第三方資源也是語言生態圈的重要組成部分。

工具鏈

完整的工具鏈對於日常開發極爲重要。Go 在此做得相當不錯,無論是編譯、格式化、錯誤檢查、幫助文檔,還是第三方包下載、更新都有對應的工具。其功能未必完善,但起碼算得上簡單易用。

內置完整測試框架,其中包括單元測試、性能測試、代碼覆蓋率、數據競爭,以及用來調優的 pprof,這些都是保障代碼能正確而穩定運行的必備利器。

除此之外,還可通過環境變量輸出運行時監控信息,尤其是垃圾回收和併發調度跟蹤,可進一步幫助我們改進算法,獲得更佳的運行期表現。

Golang 的性能

根據 Go 開發團隊和基本的算法測試,Golang 與 C 語言的性能差距大概在 10%~20% 之間。

這裏以國外的一個編程語言性能測試網站 http://benchmarksgame.alioth.debian.org/ 爲測試基準和數據源。這個網站可以對常見的編程語言進行性能比較,網站使用都是最新的語言版本和常見的一些算法。

通過對 C(GCC)、C++、Java、JavaScript 和 Golang 的測試。性能比較如下表所示,表中數據的單位爲秒,數值越小表明運行性能越好。

在這裏插入圖片描述

哪些項目使用 Golang 開發?

編程語言:

  • Golang:https://github.com/golang/go
  • delve:Golang 強大的調試器,被很多集成環境和編輯器整合。https://github.com/derekparker/delve

雲原生:

  • Docker:https://github.com/docker/docker
  • Kubernetes:https://github.com/kubernetes/kubernetes
  • Etcd:https://github.com/coreos/etcd

Web Server:

  • Beego:是一個類似 Python 的 Tornado 框架,採用了 RESTFul 的設計思路,使用Go語言編寫的一個極輕量級、高可伸縮性和高性能的 Web 應用框架。https://github.com/astaxie/beego
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章