從Python到Go:初學筆記

多事鬼間人發表於2021-08-11

本文記錄了我在學習Go的過程時的一些筆記,主要是比較Python和Go之間的差異並作簡單描述,以此使Python程式設計師對Go語言的特性有簡略的瞭解。初學難免有紕漏,歡迎各位批評指正補充交流,謝謝。

陣列和slice

Go中的陣列需要在建立時確定長度,一個更靈活的物件是slice,後者可以使用append新增,兩者的定義方式相似。

var StrArray [10]string //陣列,長度為10
var StrSlice []string //slice

slice可以根據現有的陣列(稱為底層陣列)建立,但對其的修改會導致底層陣列的改變。

指標

Go語言支援指標,用法和C一樣

結構體

結構體和Python中的Class相似,但在這一程式碼段中只能定義型別的資料佈局,方法需要定義指定接收物件的函式(見“方法”)。

type Point struct{
    X int
    Y int
}

結構體巢狀和匿名成員

在結構體中新增結構體成員會使變數的訪問變得麻煩,Go中可以不帶名稱定義結構體成員稱為匿名成員。

結合匿名成員以及方法對匿名成員的處理(包含某個結構體匿名成員的結構體可以接收該結構體的方法),匿名成員機制可以視為繼承

type ColoredPoint struct {
	Point // 匿名成員
	color string
}

var cp ColoredPoint
cp.X = 1
cp.Y = 2
cp.color = "red"

函式和方法

不同於普通的函式,方法是指定接收物件的。

包含某個結構體匿名成員的結構體可以接收該結構體的方法。

介面

定義與實現

隱式實現:滿足介面所需的方法即為實現某個介面,無需顯式宣告

type Phone interface {
    call()()
    text(str []string)(n int)
}

當某一個型別擁有如上所屬的輸入和輸出的Write方法時,即可稱其實現了Writer介面。

type iPhone struct{}

func (p iPhone) call (){
	fmt.Println("call from iPhone")
}

func (p iPhone) text (str []string){
	fmt.Println(str)
	fmt.Println("text from iPhone")
    return len(str)
}

介面的應用

介面可以被作為一個變數定義,可被賦予具體型別。

var phone Phone

// 賦值方法一
var iphone iPhone
phone = iphone
phone.call()
phone.text("test")

// 賦值方法二
phone = new(iPhone)
phone.call()
phone.text("test")

並行

goroutine

Go中每一個併發的活動稱為goroutine,不同於Python虛假的多執行緒或不穩定的多程式,goroutine被歸類為協程(Coroutine)。

並行:多程式、多執行緒、協程、非同步IO

go f()

不同於Python會自動等待各Process執行結束後退出,在Go中main函式返回時,所有的goroutine都暴力地終結,可以使用下文提及的通道阻塞或者sync的WaitGroup等待以保證各goroutine執行。

通道

通道用於goroutine間的通訊,不同於Python的Threading庫或multiporcessing庫中的Queue(佇列),Go中的通道是需要標註資料型別的。

ch := make(chan int) //定義通道,int為資料型別
ch <- x // 傳送資料
x = <- ch // 接收資料
<- ch // 接收資料並丟棄
close(ch) //關閉通道

對通道的收發操作都是阻塞的。

不同於Queue關閉後無法收發,通道關閉後無法傳送,但可以接收剩餘的資料。

無緩衝通道

ch1 := make(chan int)
ch2 := make(chan int, 0) 
// 兩者含義相同

如上定義的通道,為無緩衝通道,即一次不阻塞的傳送後,資料被接收之前,第二次傳送被阻塞。

緩衝通道

ch := make(chan int, 3) //定義通道,int為資料型別,容量為3

如上定義的通道,可以進行四次不阻塞的傳送,第五次傳送被阻塞(沒有接收的前提下)。

單向通道

為了避免誤用可以在函式的引數定義時固定通道的方向

func f(in <-chan int, out chan<- int) {} 

如上定義時,通道in對於函式f來說是隻能接收的通道,通道out對於函式f來說是隻能傳送的通道。

select多路複用

select的類似於switch,但不同的是select的分支上是阻塞著的操作而非資料。select使可以同時等待多個操作的阻塞,直到某一個分支上的操作不再阻塞。每個select只執行一個分支。

select {
case x1 <-ch1:
    // ...
case x2 <-ch2:
    // ...
case ch3 <- x3:
    // ...
default:
    // ...
}

共享變數

一句任何涉及併發的程式設計都應該遵守的話:

‘‘Do not communicate by sharing memory; instead, share memory by communicating.’’

不要通過共享記憶體來通訊,應該用通訊來共享記憶體。即應當將物件限制在順序執行的環境下(比如某個協程中)進行寫操作。

互斥鎖

也可以用鎖。

sync.Mutex

類似multiprocessing.Lock有acquire()和release(),sync.Mutex有Lock()和Unlock()。(記得用defer延遲執行Unlock()以保證解鎖的執行)

sync.RWMutex

Go提供共了一種更復雜的鎖,除了不可並行的寫鎖Lock()和Unlock(),還有可並行的讀鎖RLock()和RUnlock()。其使用類似於資料庫的二、三級封鎖協議。

sync.Once

延遲初始化,Once函式以某個函式為引數,保證這個只需要執行一次的函式在並行情況下執行且只執行一次。相同效果雖然用RWMutex也可以實現但Once更加簡便

競態檢測器 race detector

輸出一份包含所有資料競態的報告,go run/build/test時新增-race可以使用該功能。

GOMAXPROCS

確定需要使用的OS執行緒數目,可以在作為環境變數設定,或用函式runtime.GOMAXPROCS控制。

參考:

《Go程式設計語言》

相關文章