閱讀196 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Go語言與數據庫開發:01-07

在本節,我們來說一下並發。
並發程序指同時進行多個任務的程序,隨著硬件的發展,並發程序變得越來越重要

Go語言中的並發程序可以用兩種手段來實現。盡管Go對並發的支持是眾多強力特性之一,但跟蹤調試並發程序還是很困難,在線性程序中
形成的直覺往往還會使我們誤入歧途。

. Goroutines

在Go語言中,每一個並發的執行單元叫作一個goroutine。設想這裏的一個程序有兩個函數,
一個函數做計算,另一個輸出結果,假設兩個函數沒有相互之間的調用關係。一個線性的程
序會先調用其中的一個函數,然後再調用另一個。如果程序中包含多個goroutine,對兩個函
數的調用則可能發生在同一時刻。馬上就會看到這樣的一個程序。
如果你使用過操作係統或者其它語言提供的線程,那麼你可以簡單地把goroutine類比作一個
線程,這樣你就可以寫出一些正確的程序了

當一個程序啟動時,其主函數即在一個單獨的goroutine中運行,我們叫它main goroutine。新
的goroutine會用go語句來創建。在語法上,go語句是一個普通的函數或方法調用前加上關鍵
字go。go語句會使其語句中的函數在一個新創建的goroutine中運行。而go語句本身會迅速地
完成。
f() // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait

. Channels

如果說goroutine是Go語音程序的並發體的話,那麼channels它們之間的通信機製。一個
channels是一個通信機製,它可以讓一個goroutine通過它給另一個goroutine發送值信息。每
個channel都有一個特殊的類型,也就是channels可發送數據的類型。一個可以發送int類型數
據的channel一般寫為chan int。
使用內置的make函數,我們可以創建一個channel:
ch := make(chan int) // ch has type 'chan int'

和map類似,channel也一個對應make創建的底層數據結構的引用。當我們複製一個channel
或用於函數參數傳遞時,我們隻是拷貝了一個channel引用,因此調用者何被調用者將引用同
一個channel對象。和其它的引用類型一樣,channel的零值也是nil。
兩個相同類型的channel可以使用==運算符比較。如果兩個channel引用的是相通的對象,那
麼比較的結果為真。一個channel也可以和nil進行比較。
一個channel有發送和接受兩個主要操作,都是通信行為。一個發送語句將一個值從一個
goroutine通過channel發送到另一個執行接收操作的goroutine。發送和接收兩個操作都是
用 <- 運算符。在發送語句中, <- 運算符分割channel和要發送的值。在接收語句中, <- 運
算符寫在channel對象之前。一個不使用接收結果的接收操作也是合法的。
ch <- x // a send statement
x = <-ch // a receive expression in an assignment statement
<-ch // a receive statement; result is discarded

Channel還支持close操作,用於關閉channel,隨後對基於該channel的任何發送操作都將導
致panic異常。對一個已經被close過的channel之行接收操作依然可以接受到之前已經成功發
送的數據;如果channel中已經沒有數據的話講產生一個零值的數據。
使用內置的close函數就可以關閉一個channel:
close(ch)
以最簡單方式調用make函數創建的時一個無緩存的channel,但是我們也可以指定第二個整
形參數,對應channel的容量。如果channel的容量大於零,那麼該channel就是帶緩存的
channel。

ch = make(chan int) // unbuffered channel
ch = make(chan int, 0) // unbuffered channel
ch = make(chan int, 3) // buffered channel with capacity 3

.不帶緩存的Channels

一個基於無緩存Channels的發送操作將導致發送者goroutine阻塞,直到另一個goroutine在相
同的Channels上執行接收操作,當發送的值通過Channels成功傳輸之後,兩個goroutine可以
繼續執行後麵的語句。反之,如果接收操作先發生,那麼接收者goroutine也將阻塞,直到有
另一個goroutine在相同的Channels上執行發送操作。
基於無緩存Channels的發送和接收操作將導致兩個goroutine做一次同步操作。因為這個原
因,無緩存Channels有時候也被稱為同步Channels。當通過一個無緩存Channels發送數據
時,接收者收到數據發生在喚醒發送者goroutine之前.

在討論並發編程時,當我們說x事件在y事件之前發生(happens before),我們並不是說x事
件在時間上比y時間更早;我們要表達的意思是要保證在此之前的事件都已經完成了,例如在
此之前的更新某些變量的操作已經完成,你可以放心依賴這些已完成的事件了。
當我們說x事件既不是在y事件之前發生也不是在y事件之後發生,我們就說x事件和y事件是並
發的。這並不是意味著x事件和y事件就一定是同時發生的,我們隻是不能確定這兩個事件發
生的先後順序。在下一章中我們將看到,當兩個goroutine並發訪問了相同的變量時,我們有
必要保證某些事件的執行順序,以避免出現某些並發問題。

.串聯的Channels(Pipeline)

Channels也可以用於將多個goroutine鏈接在一起,一個Channels的輸出作為下一個Channels
的輸入。這種串聯的Channels就是所謂的管道(pipeline)。
如果發送者知道,沒有更多的值需要發送到channel的話,那麼讓接收者也能及時知道沒有多
餘的值可接收將是有用的,因為接收者可以停止不必要的接收等待
這可以通過內置的close
函數來關閉channel實現:
close(naturals)
當一個channel被關閉後,再向該channel發送數據將導致panic異常。當一個被關閉的channel
中已經發送的數據都被成功接收後,後續的接收操作將不再阻塞,它們會立即返回一個零
值。關閉上麵例子中的naturals變量對應的channel並不能終止循環,它依然會收到一個永無
休止的零值序列,然後將它們發送給打印者goroutine。
沒有辦法直接測試一個channel是否被關閉,但是接收操作有一個變體形式:它多接收一個結
果,多接收的第二個結果是一個布爾值ok,ture表示成功從channels接收到值,false表示
channels已經被關閉並且裏麵沒有值可接收。使用這個特性,我們可以修改squarer函數中的
循環代碼,當naturals對應的channel被關閉並沒有值可接收時跳出循環,並且也關閉squares
對應的channel.

// Squarer
go func() {
for {
x, ok := <-naturals
if !ok {
break // channel was closed and drained
}
squares <- x * x
}
close(squares)
}()

因為上麵的語法是笨拙的,而且這種處理模式很場景,因此Go語言的range循環可直接在
channels上麵迭代。使用range循環是上麵處理模式的簡潔語法,它依次從channel接收數
據,當channel被關閉並且沒有值可接收時跳出循環。

最後更新:2017-10-13 23:04:01

  上一篇:go  MHA failover流程
  下一篇:go  大家好,給大家介紹一下,這是我的智能夥伴…..