協程goroutine
不由OS調度,而是用戶層自行釋放CPU,從而在執行體之間切換。Go在底層進行協助實現
涉及系統調用的地方由Go標准庫協助釋放CPU
總之,不通過OS進行切換,自行切換,系統運行開支大大降低
通道channel
並發編程的關鍵在於執行體之間的通信,go通過通過channel進行通信
channel可以認為類似其他OS體系中的消息隊列,只不過在go中原生支持,因而易用
消息隊列有哪些值得關注的地方?常見問題包括創建、關閉或刪除、阻塞、超時、優先級等,golang中也不例外。羅列如下:
可否探測隊列是滿或空?或者說是否可以不阻塞地嘗試讀寫?
讀阻塞和寫阻塞時關閉會怎樣?
關閉后未讀取的消息會被拋棄?
往關閉的channel發送數據或讀取數據會怎樣?
怎樣探測channel的關閉?
兩個地方讀或寫阻塞同一個channel,有沒有優先級?
是否可以設定阻塞的超時時間?
阻塞時怎樣可以被彈出來?比如某些信號?
事實上,知道存在這些問題並進行分門別類是重要的,但知道這些問題的答案卻不緊要,因為一般不會太過古怪,使用時臨時試驗一下即可。
已知的部分答案:
好像不能不阻塞地嘗試讀寫
關閉會導致退出阻塞(似乎是一個不錯的特性)
可以探測關閉
channel本身不能設定超時
了解這些似乎已經足夠。
與眾不同的地方值得我們重點留意,包括:
除基本讀寫方式外,還有哪些特別的讀寫方式?在阻塞、關閉、超時方面又有什么不同?發現了select、range兩個關鍵字
推薦的多通道讀
推薦的同步方法
推薦的超時方法
select
select可以實現無阻塞的多通道嘗試讀寫,以及阻塞超時
var c, c1, c2, c3
chan
int
var i1, i2
int
select {
case i1 = <-c1: //如果能走通任何case則隨機走一個
print( "received ", i1, " from c1\n" )
case c2 <- i2:
print( "sent ", i2, " to c2\n" )
case i3, ok := (<-c3):
if ok {
print( "received ", i3, " from c3\n" )
}
else {
print( "c3 is closed\n")
}
default: // 如果case都阻塞,則走default,如果無default,則阻塞在case
// default中可以不讀寫任何通道,那么只要default提供不阻塞的出路,就相當於實現了對case的無阻塞嘗試讀寫
print( "no communication\n")
}
實現阻塞超時的方法是,只要不給default出路,而在case中實現一個超時
timeout :=
make (
chan
bool, 1)
go
func () {
time.Sleep(1e9) // 這是等待1秒鍾
timeout <-
true
}()
// 用timeout這個通道作為阻塞超時的出路
select {
case <-ch:
// 處理從ch中讀到的數據
case <-timeout:
// 如果case都阻塞了,那么1秒鍾后會從這里找到出路
}
range
range可以在for循環中讀取channel
Go文檔的翻譯文是:對於信道,其迭代值產生為在該信道上發送的連續值,直到該信道被關閉。若該信道為 nil,則range表達式將永遠阻塞
經過試驗,range會阻塞,並且可以通過關閉channel來解除阻塞。
package main
import (
"fmt"
)
func main() {
ch :=
make(
chan
int )
go
func () {
for i := 0; i < 10; i++ {
ch <- i
}
}()
for w :=
range ch {
fmt.Println( "fmt print" , w)
if w > 5 {
//break // 在這里break循環也可以
close(ch)
}
}
fmt.Println( "after range or close ch!" )
}
Golang的並發編程還有其他細節,但以上是最主要脈絡。