通道型別是Go自帶的,相當於是一個先進先出的佇列,同時唯一一個可以滿足併發安全性的型別。宣告一個通道型別變數的時候,首先需要確定通道型別的元素型別,然後還要確定通道的容量,當然預設容量是0。
初始化
使用make進行初始化,如下所示:
c := make(chan int)
c := make(chan string, 10)
複製程式碼
如果不指定容量,預設通道的容量是0,這種通道也成為非緩衝通道。
通道的傳送和接收特性
-
對於同一個通道,傳送操作之間是互斥的,接收操作之間也是互斥的。
簡單來說就是在同一時刻,Go的執行系統只會執行對同一個通道的任意個傳送操作中的某一個,直到這個元素值被完全複製進該通道之後,其他傳送操作才會執行。針對接收操作也是這樣。 對於通道中的同一個值,傳送操作和接收操作也是互斥的。如正在被複制進通道但還未複製完成的元素值,這時接收方也不會看到和取走。
tips 元素值從外界進入通道會被複制。也就是說進入通道的並不是在接收操作符右邊的那個元素值,而是他的副本。
-
傳送操作和接收操作中對元素值的處理都是不可分割的。 不可分割意思就是傳送操作要麼還沒複製元素,要麼已經複製完畢,不會出現值只複製了一部分的情況。
-
傳送操作在完全完成之前會被阻塞。接收操作也是如此。 傳送操作包括,“複製元素值”,“放置副本到通道內” 二個步驟。在這二個步驟完成之前,傳送操作會一直阻塞,他之後的程式碼是不會執行的。 接收操作包括“複製通道內元素值”,“放置副本到接收方”,“刪除原值” 三個操作。這三個操作在完成之前也是會一直阻塞的。
tips: 上面講的複製都屬於淺拷貝。淺拷貝只是拷貝值以及值中直接包含的東西,深拷貝就是把所有深層次的結構一併拷貝,Golang只有淺拷貝。
傳送操作和接收操作在什麼時候會被阻塞呢
對於快取通道
- 如果通道已滿,所有的傳送操作就會阻塞,直到通道中有元素被取走
- 如果通道已空,所有的接收操作就會阻塞,直到通道中有新的元素
對於非快取通道
- 無論傳送操作還是接受操作一開始就是阻塞的,只有配對的操作出現才會開始執行。
收發操作何時會引起panic
-
通道關閉,在進行傳送操作會引發panic
-
關閉一個已經關閉的通道也會引發panic 更具體地說,當我們把接收表示式的結果同時賦給兩個變數時,第二個變數的型別就是一定bool型別。它的值如果為false就說明通道已經關閉,並且再沒有元素值可取了。
注意,如果通道關閉時,裡面還有元素值未被取出,那麼接收表示式的第一個結果,仍會是通道中的某一個元素值,而第二個結果值一定會是true。因此,通過接收表示式的第二個結果值,來判斷通道是否關閉是可能有延時的。
package main import "fmt" func main() { ch1 := make(chan int, 2) // 傳送方。 go func() { for i := 0; i < 10; i++ { fmt.Printf("Sender: sending element %v...\n", i) ch1 <- i } fmt.Println("Sender: close the channel...") close(ch1) }() // 接收方。 for { elem, ok := <-ch1 if !ok { fmt.Println("Receiver: closed channel") break } fmt.Printf("Receiver: received an element: %v\n", elem) } fmt.Println("End.") } 複製程式碼
Channel引起的死鎖的常見場景
死鎖是指兩個或兩個以上的協程的執行過程中,由於競爭資源或由於彼此通訊而造成的一種阻塞的現象,若無外力作用,他們將無法推進下去,解決死鎖的方法是加鎖。。結合上面講的channel相關知識,大家可以思考一下面情況為何為引起死鎖。
場景1:一個通道在一個go協程讀寫
func main() {
c:=make(chan int)
c<-666
<-c
}
複製程式碼
場景二:go程開啟之前使用通道
func main() {
c:=make(chan int)
c<-666
go func() {
<-c
}()
}
複製程式碼
場景三:通道1中呼叫了通道2,通道2中呼叫通道1
func main() {
c1,c2:=make(chan int),make(chan int)
go func() {
for {
select{
case <-c1:
c2<-10
}
}
}()
for {
select{
case <-c2:
c1<-10
}
}
}
複製程式碼