咱們在說「通道」時通常說的都是雙向通道,即:既能夠發也能夠收的通道。這裏的「發」和「收」是站在操做通道的代碼的角度說的。算法
所謂單向通道,就是隻能發不能收,或者只能收不能發的通道。安全
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類型的通道,獲得該通道的程序,只能從通道中接收元素值。
這是對函數調用方的約束。
intChan2 := getIntChan() for elem := range intChan2 { fmt.Printf("The element in intChan2: %v\n", elem) }
上面的for語句稱爲帶有range子句的for語句。
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表達式,都至少會包含一個表明發送操做的發送表達式或者一個表明接收操做的接收表達式,同時也可能會包含其餘的表達式。好比,若是case表達式是包含了接收表達式的短變量聲明時,那麼在賦值符號左邊的就能夠是一個或兩個表達式,不過此處的表達式的結果必須是能夠被賦值的。當這樣的case表達式被求值時,它包含的多個表達式總會以從左到右的順序被求值。
select語句包含的候選分支中的case表達式都會在該語句執行開始時先被求值,而且求值的順序是依從代碼編寫的順序從上到下的。結合上一條規則,在select語句開始執行時,排在最上邊的候選分支中最左邊的表達式會最早被求值,而後是它右邊的表達式。僅當最上邊的候選分支中的全部表達式都被求值完畢後,從上邊數第二個候選分支中的表達式纔會被求值,順序一樣是從左到右,而後是第三個候選分支、第四個候選分支,以此類推。
對於每個case表達式,若是其中的發送表達式或者接收表達式在被求值時,相應的操做正處於阻塞狀態,那麼對該case表達式的求值就是不成功的。在這種狀況下,咱們能夠說,這個case表達式所在的候選分支是不知足選擇條件的。
僅當select語句中的全部case表達式都被求值完畢後,它纔會開始選擇候選分支。這時候,它只會挑選知足選擇條件的候選分支執行。若是全部的候選分支都不知足選擇條件,那麼默認分支就會被執行。若是這時沒有默認分支,那麼select語句就會當即進入阻塞狀態,直到至少有一個候選分支知足選擇條件爲止。一旦有一個候選分支知足選擇條件,select語句(或者說它所在的 goroutine)就會被喚醒,這個候選分支就會被執行。
若是select語句發現同時有多個候選分支知足選擇條件,那麼它就會用一種僞隨機的算法在這些分支中選擇一個並執行。注意,即便select語句是在被喚醒時發現的這種狀況,也會這樣作。
一條select語句中只可以有一個默認分支。而且,默認分支只在無候選分支可選時纔會被執行,這與它的編寫位置無關。
select語句的每次執行,包括case表達式求值和分支選擇,都是獨立的。不過,至於它的執行是不是併發安全的,就要看其中的case表達式以及分支中,是否包含併發不安全的代碼了
當第二個boolean參數爲false的時候,在相應的case中設置chan爲nil零值,再次case求值的時候會遭遇阻塞,會屏蔽該case
經過定義標籤,配合goto或者break能實如今同一個函數內任意跳轉,故能夠跳出多層嵌套的循環。