golang通道channel高階
單向通道
only_read := make(<-chanint,1)//只能傳送通道
only_write := make(chan<- int, 1) //只能接受通道
問:單向通道的用途?
答:
單向通道的主要用途就是約束其他程式碼的行為。
func SendInt(ch chan<- int) { ch <- rand.Intn(1000) }
在實際場景中,這種約束一般會出現在介面型別宣告中的某個方法定義上。 demo:
type Notifier interface{ SendInt(ch chan<- int) }
在這裡,Notifier介面中的SendInt方法只會接受一個傳送通道作為引數,所以,在該介面中的所有實現型別中的SendInt方法都會受到限制。這種約束方法還是很有用的,尤其是在我們編寫模版程式碼或者可擴充套件程式庫的時候。
順便說一下,我們在呼叫SendInt函式的時候,只需要把一個元素型別匹配的雙向通道傳給它就行了。沒必要用傳送通道,因為Go語言在這種情況下自動地把雙向通道轉換為函式所需的單向通道。
intChan1 := make(chan int, 3) SendInt(intChan1)
在另一方面,我們還可以在函式宣告的結果列表中使用單向通道。如下所示:
func getIntChan() <-chan int { num := 5 ch := make(chan int, num) for i := 0; i< num; i++{ ch <- i } close(ch) return ch }
函式getIntChane會返回一個<-chan int型別的通道,這就意味著得到該通道的程式,只能從通道中接受元素值。這實際上就是對函式呼叫方的一種約束了。
另外。我們在Go語言中還可以宣告函式型別,如果我們在函式型別中使用了單向通道,那麼就相當於在約束所有實現了這個函式型別的函式。
intChan2 := getIntChane() for elem := range intChan2{ fmt.Printf("The element int intChan2: %v\n", elem) }
for range遍歷通道要注意的點:
1.for語句會不斷嘗試從intChan2中取出元素值,即使intChan2被關閉,它也會在取出所有剩餘的元素值之後再結束關閉。
2.當intChan2中沒有元素值時,它會被阻塞在有for關鍵字的那一行,直搗有新的元素可取。
3.假設intChan2的值為nil,那麼它會被永遠阻塞在有for關鍵字的那一行。
問:select語句與通道聯用,應注意什麼?
答:
// 通道陣列 intChannels := [3]chan int { make(chan int, 1), make(chan int, 1), make(chan int, 1), } // 隨機選擇一個通道,並向它傳送元素子 index := rand.Int(3) fmt.Printf("The index:%d \n",index) intChannels[index] <-index // 哪一個通道中有可取的元素值,哪個對應的分支就被執行。 select { case<-intChannels[0]: fmt.Println("The first candidate case is selected.") case<-intChannels[1]: fmt.Println("The second candidate case is selected.") case<-intChannels[2]: fmt.Println("The third candidate case is selected.") default: fmt.Println("No candidate case is selected") }
在使用select語句時,應該注意:
1. 如果加入了default分支,那麼無論涉及通道操作的表示式是否有阻塞,select語句都不會被阻塞。如果那幾個表示式被阻塞了,或者說都沒有滿足求值的條件,那麼預設分支就會被選中並執行。
2. 如果沒有加入預設分支,那麼一旦所有的case表示式都沒有滿足求值條件,那麼select語句就會被阻塞。直到至少有一個case表示式滿足條件為止。
3. 我們可能會因為通道關閉了,而直接從通道接收到一個其元素型別的零值。所以,在很多時候,我們需要通過接受表示式的第二個結果值來判斷通道是否關閉。一旦發現某個通道關閉了,我們就應該及時地遮蔽掉對應的分支或者採取其他措施。這對於程式邏輯和程式效能都是有好處的。
4. select語句只能對其中的一個每一個case表示式各求值一次。所以,如果我們想連續或定時地操作其中的通道時,就往往需要在for語句中嵌入select語句的方法實現。但這時要注意,簡單地在select語句的分支中使用break語句,只能結束當前的select語句的執行,而不會對外層的for語句產生作用。這種錯誤的用法可能會讓這個for語句無休止地執行下去。
錯誤示例: intChan:= make(chan int, 1) // 1秒鐘後關閉通道 time.AfterFunc(time.Second, func(){ close(intChan) }) select { case _, ok := <-intChan: if !ok { break;//錯誤,不能跳出for,只能跳出select } fmt.Println("The candidate case is selected.") }
select分支選擇規則細節:
1. select語句候選分支的case表示式 求值順序是依次從程式碼的編寫順序從上到下的。排在最上方的候選分支中最左邊的表示式會最先被求值,然後是它右邊的表示式。然後依次順序向下的候選分支開始求值。
2. 進當select語句中的所有case表示式都被求值完畢後,它才會開始選擇候選分支。這時候,它只會挑選滿足選擇條件的候選分支執行。如果所有的候選分支都不滿足條件,那麼預設分支就會被執行。如果這時沒有預設分支,那麼select分支語句就會立即進入阻塞狀態,直到至少有一個候選分支滿足選擇條件為止。一旦有一個候選分支滿足選擇條件,select語句(或者說它所在的goroutine)就會被喚醒,這個候選分支就會被執行。
3. 如果select語句發現同時有多個候選分支滿足選擇條件,那麼它會用一個偽隨機的演算法在這些分支中選擇一個並執行。注意,即使select語句是在被喚醒時發現的這種情況,也會這麼做。
4. select語句的每次執行,包括case表示式求值和分支選擇,都是獨立的,不過,至於它的執行是否是併發安全的,就要看其中的case表示式以及分支中,是否包含併發不安全的程式碼了。
問題:1. 如果在select語句中發現某個通道已關閉,那麼應該怎樣遮蔽它所在的分支?
回答:當判斷表示式的第二個引數為false時,代表通道已經關閉,這時候將該channel賦值為nil,那麼每次從該nil channel接受時就會阻塞,select會忽略阻塞的通道。
問題:2.在select語句與for語句聯用時,怎麼直接退出最外層的for語句?
回答:通過定義標籤,配合goto或者breal語句能實現在同一個函式內任意跳轉,就可以跳出多層迴圈了。