channel的高級玩法

單向通道

咱們在說「通道」時通常說的都是雙向通道,即:既能夠發也能夠收的通道。這裏的「發」和「收」是站在操做通道的代碼的角度說的。算法

所謂單向通道,就是隻能發不能收,或者只能收不能發的通道。安全

定義單向通道

var uselessChan = make(chan<- int , 1)  //發送通道:只能發不能收

var uselessChan = make(<-chan int , 1)  //接收通道:只能收不能發

在關鍵字chan的前面或後面加上通道收發操做符便可表示。併發

通道就是爲了傳遞數據而存在的,聲明一個只有一端能用的通道沒有任何意義。less

單向通道的用途

歸納地講:單向通道最主要的用途就是約束其餘代碼的行爲。主要在函數中約束代碼的行爲。函數

解析

func SendInt(ch chan<- int) {
     ch <- rand.Intn(1000)
}

SendInt函數的參數是一個chan<- int 類型的通道。在這個函數中的代碼就只能向參數ch發送元素值,而不能從ch裏接收元素值。這就起到了約束函數行爲的做用。設計

在實際場景中:這種約束通常會出如今接口類型聲明的某個方法定義上。code

type Notifier interface {
     SendInt(ch chan<- int)
}

咱們在接口中定義的方法中若是使用了單向通道類型,那麼就至關於對這個接口的全部實現作了約束。這個約束方式在編寫模板代碼或者可擴展的程序庫的時候頗有用。接口

咱們雖然在方法中聲明接收單向通道做爲參數,可是,實際向方法傳遞參數的時候,只須要把一個元素類型匹配的雙向通道傳遞給它就好了,由於Go語言在這種狀況下會自動地把雙向通道轉換爲函數所需的單向通道。element

咱們還能夠在函數聲明的結果列表中使用單向通道。get

func getIntChan() <-chan int{
       num := 5
	   ch := make(chan int, num)
	   for i := 0; i<num; i++ {
	         ch <- i
	   }
	   close(ch)
	   return ch
}

函數getIntChan會返回一個<-chan int類型的通道,獲得該通道的程序,只能從通道中接收元素值。

這是對函數調用方的約束。

帶range子句的for語句與通道聯用

intChan2 := getIntChan()
for elem := range intChan2 {
	fmt.Printf("The element in intChan2: %v\n", elem)
}

上面的for語句稱爲帶有range子句的for語句。

  • 1、這樣一條for語句會不斷地嘗試從intChan2種取出元素值,即便intChan2被關閉,它也會在取出全部剩餘的元素值以後再結束執行。
  • 2、當intChan2中沒有元素值時,它會被阻塞在有for關鍵字的那一行,直到有新的元素值可取。
  • 3、假設intChan2的值爲nil,那麼它會被永遠阻塞在有for關鍵字的那一行。

select語句與通道聯用

select語句只能與通道聯用,它通常由若干個分支組成。select語句有2中分支:候選分支,以關鍵字case開頭,後面跟一個case表達式和一個冒號,下一行寫要執行的語句;默認分支:以關鍵字default開頭,後面跟一個冒號,下一行寫要執行的語句。

select語句是專門爲通道設計的,每一個case表達式中都只能包含操做通道的表達式。

// 準備好幾個通道。
intChannels := [3]chan int{
	make(chan int, 1),
	make(chan int, 1),
	make(chan int, 1),
}
// 隨機選擇一個通道,並向它發送元素值。
index := rand.Intn(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 elem := <-intChannels[2]:
	fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default:
	fmt.Println("No candidate case is selected!")
}
  • 若是沒有加默認分支,一旦全部的case表達式都沒有知足求值條件,那麼select語句就會被阻塞。直到至少有一個case表達式知足條件爲止。
  • 加入了默認分支,不管通道操做的表達式是否阻塞,select語句都不會被阻塞。若是那幾個表達式都阻塞了,或者說都沒有知足求值的條件,那麼默認分支就會被選擇和執行
  • 前面咱們瞭解到:咱們可能會由於通道關閉了,而直接從通道中接收到一個元素類型的零值。因此在不少時候,咱們須要經過接收表達式的第二個結果值來判斷通道是否已經關閉。一旦發現一個通道關閉了,咱們就應該及時屏蔽掉對應的分支或者採起其餘措施。
  • select語句只能對其中的每個case表達式各求值一次。因此,若是咱們想連續或定時地操做其中的通道的話,就每每須要經過在for語句中嵌入select語句的方式實現。但這時要注意,簡單地在select語句的分支中使用break語句,只能結束當前的select語句的執行,而並不會對外層的for語句產生做用。這種錯誤的用法可能會讓這個for語句無休止地運行下去。

select語句的分支選擇規則都有哪些?

規則以下面所示。

  1. 對於每個case表達式,都至少會包含一個表明發送操做的發送表達式或者一個表明接收操做的接收表達式,同時也可能會包含其餘的表達式。好比,若是case表達式是包含了接收表達式的短變量聲明時,那麼在賦值符號左邊的就能夠是一個或兩個表達式,不過此處的表達式的結果必須是能夠被賦值的。當這樣的case表達式被求值時,它包含的多個表達式總會以從左到右的順序被求值。

  2. select語句包含的候選分支中的case表達式都會在該語句執行開始時先被求值,而且求值的順序是依從代碼編寫的順序從上到下的。結合上一條規則,在select語句開始執行時,排在最上邊的候選分支中最左邊的表達式會最早被求值,而後是它右邊的表達式。僅當最上邊的候選分支中的全部表達式都被求值完畢後,從上邊數第二個候選分支中的表達式纔會被求值,順序一樣是從左到右,而後是第三個候選分支、第四個候選分支,以此類推。

  3. 對於每個case表達式,若是其中的發送表達式或者接收表達式在被求值時,相應的操做正處於阻塞狀態,那麼對該case表達式的求值就是不成功的。在這種狀況下,咱們能夠說,這個case表達式所在的候選分支是不知足選擇條件的。

  4. 僅當select語句中的全部case表達式都被求值完畢後,它纔會開始選擇候選分支。這時候,它只會挑選知足選擇條件的候選分支執行。若是全部的候選分支都不知足選擇條件,那麼默認分支就會被執行。若是這時沒有默認分支,那麼select語句就會當即進入阻塞狀態,直到至少有一個候選分支知足選擇條件爲止。一旦有一個候選分支知足選擇條件,select語句(或者說它所在的 goroutine)就會被喚醒,這個候選分支就會被執行。

  5. 若是select語句發現同時有多個候選分支知足選擇條件,那麼它就會用一種僞隨機的算法在這些分支中選擇一個並執行。注意,即便select語句是在被喚醒時發現的這種狀況,也會這樣作。

  6. 一條select語句中只可以有一個默認分支。而且,默認分支只在無候選分支可選時纔會被執行,這與它的編寫位置無關。

  7. select語句的每次執行,包括case表達式求值和分支選擇,都是獨立的。不過,至於它的執行是不是併發安全的,就要看其中的case表達式以及分支中,是否包含併發不安全的代碼了

若是在select語句中發現某個通道已關閉,那麼應該怎樣屏蔽掉它所在的分支?

當第二個boolean參數爲false的時候,在相應的case中設置chan爲nil零值,再次case求值的時候會遭遇阻塞,會屏蔽該case

在select語句與for語句聯用時,怎樣直接退出外層的for語句?

經過定義標籤,配合goto或者break能實如今同一個函數內任意跳轉,故能夠跳出多層嵌套的循環。

相關文章
相關標籤/搜索