Go 當前引起了很多關注。讓我們看一下 Go 好的部分。

我最近用 Go 寫了一個 SSH 服務器 ,在其中啓動容器。該項目已經發展到很大規模,並且我還向 Go 發起了 PR ,以修復我發現的錯誤。在積累了比 “Hello world!” 更多的經驗之後,現在我有信心寫出我真正喜歡的 Go 語言部分。

跨平臺

Go 引起我注意的原因之一是其構建系統。Java 的最初承諾是它是跨平臺的,但是它需要安裝運行時的事實顯然是一個失敗。而 Go 編譯爲本地二進制文件。在 Windows 上,您將獲得一個 .exe 文件,在 Linux 上,您將獲得一個 ELF 二進制文件,依此類推。而且,除非您使用 cgo,否則 Go 程序可以在幾乎沒有外部依賴的情況下運行。無需安裝任何 .dll 或 .so 文件,Go 程序即可 直接使用

大部分時候,除了某些函數需要 libc 外,Go 程序可以在完全沒有外部依賴的情況下運行。

Go 可以用來構建真正的跨平臺二進制文件而無需安裝笨拙的運行時(例如使用 Java 或 Python),這一事實是 Go 的主要吸引點。

Goroutines 和 Channels

當我開始使用 Go 語言時,我意識到它對併發的處理是多麼的酷。傳統上,您將使用線程或單獨的進程來同時運行多個任務(例如 Java,C,C ++)。另外,您也可以依靠協作式多任務處理(例如 Javascript)來達到相同的效果。

對於線程和進程,操作系統必須執行的每個切換都會造成資源損耗。這稱爲上下文切換。換句話說,一個使用大量線程的粗心程序員將帶來性能問題。

另一方面,協作多任務將在單個線程上運行。每當一項任務必須等待某件事時,另一項任務就會運行。如果一個任務佔用了 CPU,其他任務將被餓死。

Go 以巧妙的方式將兩者結合在一起。讓我們以以下示例爲例:

func main() {
    go someOtherFunction()
}

注意 go 關鍵字。通過使用此關鍵字,someOtherFunction() 可在 goroutine 中運行。想象一下 Go 作爲線程池處理併發的方式。每當您運行 goroutine 時,它將在這些線程之一中運行。這樣 Go 可以優化線程的使用以提高性能。

爲了促進 goroutine 之間的數據傳輸,Go 引入了通道(channel),通道是應用程序中的消息隊列,用於發送數據。

func main() {
    chan done <- bool

    go func() {
        time.Sleep(2 * time.Second)
        done <- true
    }()

    //This will wait until the goroutine finishes
    <- done
}

從上面的代碼中可以看到, <- channelname 將堵塞當前 goroutine 的執行,直到有可用數據爲止,這使得併發編程非常容易。

如果您對更多細節感興趣,請查看 channel上下文互斥鎖

指針,Defer 和垃圾收集

當提到指針時,首先想到的是 C 或 C++。通常,這種記憶並不愉快。

在 Go 中,指針更像是引用。指針並非總是將數據複製到變量中,而是指向原始的內存。不管傳遞包含指針的變量多少次,任何修改都將始終更改原始值。

讓我們看一個例子:

someVar := &someStruct{}

現在,變量包含指向該結構的指針。傳遞時,無論您複製指針多少次,它始終指代相同的內存空間。

但是,與 C 指針不同,Go 指針在不再需要時會自動進行垃圾回收。您無需擔心使用後釋放或緩衝區溢出漏洞,這些在 Go 中都不是問題。太棒了!

此外,您還可以使用 defer 語句來幫助您進行函數清除。考慮以下函數:

func foo() error {
    close := func() {
        // Do somehing to clean up stuff
    }
    err := doSomething()
    if err != nil {
        close()
        return err
    }
    // Do something else
    close()
}

如您所見,我們在此函數中調用了 close() 兩次。如果 foo 函數有多個出口(返回),則需要爲每個出口重複 close() 調用。

defer 語句完全可以解決此問題:

func foo() error {
    close := func() {
        // Do somehing to clean up stuff
    }
    defer close()

    err := doSomething()
    if err != nil {
        return err
    }
    // Do something else
}

defer 語句保證 close 總是會被調用。

多返回值

這看似沒什麼,但是在編程語言中卻很少見。

sshConn, chans, reqs, err := ssh.NewServerConn(tcpConn, config)

有什麼理由不喜歡?

OOP(好的部分)

儘管 Go 沒有類的概念,但仍然可以編寫面向對象的代碼。

假設有以下 Java 代碼:

class TreeNode {
    private List<TreeNode> nodes = new ArrayList();
    public void addChild(child TreeNode) {
        nodes.add(child)
    }
}

在 Go 中,類似的代碼如下所示:

type TreeNode struct {
    children []treeNode
}

func New() *TreeNode {
        return &TreeNode{}
}

func (treeNode *TreeNode) AddChild(child * TreeNode) {
    treeNode.children = append(treeNode.children, child)
}

Go 將( treeNode *TreeNode )部分稱爲接收器。Go 中的 Receiver 可以使用與其他語言中的 this 關鍵字非常相似的任何數據類型和功能。

Slices

和許多其他低級語言一樣,Go 將數組實現爲固定大小的元素列表。創建後無法更改其大小。

另一方面,切片(Slice)是使它們動態化的技巧。當切片已滿時,Go 會創建一個更大的切片新副本。Go 以儘可能少的複製的方式優化過程。

此外,Go slice 還具有創建不佔用額外內存的子切片的簡潔功能。這些切片引用原始的數組。如果更改切片中的數據,則原始數據也將更改。

import "fmt"

func main() {
    data := []string{"a", "b", "c", "d"}
    d := data[2:3]
    // Will print 
    fmt.Printf("%v", d)
    d[0] = "f"
    //Will print [a b f d]
    fmt.Printf("%v", data)
}

如果您想深入瞭解,請繼續閱讀 Go by Example

選擇 Go 的原因之一是庫數量衆多。SSH 客戶端和服務器庫? 。適用於 AWS 的 SDK? 同樣有 。GitHub 操作庫? 當然有 。讓我們嘗試一些非常少用的東西……FastCGI 協議實現如何? 當然也有

我可以繼續,但是沒多大必要了。Go 的普及無疑對生態系統有所幫助。

工具

構建 Go 擁有大量可用的工具。您擁有從 自動代碼格式化 ,測試到 完整發布工具 的全套工具。幾乎所有的都有很多工具。

結論

在代碼組織方面,Go 當然有其缺點。但是,它特別適合用於各種任務的高性能軟件開發。

那 Go 具體有哪些缺點呢?下次我們“噴一噴” Go 的缺點。

原文鏈接:https://pasztor.at/blog/go-is-awesome

編譯:polaris

歡迎關注我的公衆號:

相關文章