您好,登錄后才能下訂單哦!
這篇文章主要介紹了Go中的channel怎么聲明和使用的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Go中的channel怎么聲明和使用文章都會有所收獲,下面我們一起來看看吧。
Go語言中的通道(channel)是一種特殊的類型。
在任何時候,同時只能有一個 goroutine 訪問通道進行發送和獲取數據。goroutine 間通過通道就可以通信。
通道像一個傳送帶或者隊列,總是遵循先入先出(First In First Out)的規則,保證收發數據的順序。
(1)channel本身是一個隊列,先進先出
(2)線程安全,不需要加鎖
(3)本身是有類型的,string, int 等,如果要存多種類型,則定義成 interface類型
(4)channel是引用類型,必須make之后才能使用,一旦 make,它的容量就確定了,不會動態增加!!它和map,slice不一樣
特點:
(1)一旦初始化容量,就不會改變了。
(2)當寫滿時,不可以寫,取空時,不可以取。
(3)發送將持續阻塞直到數據被接收
把數據往通道中發送時,如果接收方一直都沒有接收,那么發送操作將持續阻塞。Go 程序運行時能智能地發現一些永遠無法發送成功的語句并做出提示
(4)接收將持續阻塞直到發送方發送數據。
如果接收方接收時,通道中沒有發送方發送數據,接收方也會發生阻塞,直到發送方發送數據為止。
(5)每次接收一個元素。
通道一次只能接收一個數據元素。
package main import ( "fmt" ) func main() { //演示一下管道的使用 //1. 創建一個可以存放3個int類型的管道 var intChan chan int intChan = make(chan int, 3) //2. 看看intChan是什么 fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan) //3. 向管道寫入數據 intChan<- 10 num := 211 intChan<- num intChan<- 50 // //如果從channel取出數據后,可以繼續放入 <-intChan intChan<- 98//注意點, 當我們給管寫入數據時,不能超過其容量 //4. 看看管道的長度和cap(容量) fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3 //5. 從管道中讀取數據 var num2 int num2 = <-intChan fmt.Println("num2=", num2) fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 2, 3 //6. 在沒有使用協程的情況下,如果我們的管道數據已經全部取出,再取就會報告 deadlock num3 := <-intChan num4 := <-intChan //num5 := <-intChan fmt.Println("num3=", num3, "num4=", num4/*, "num5=", num5*/) }
fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)
這句代碼顯示:channel其實和指針一樣,本身存放在一個內存單元中,有它的地址,而它的值是一個 int類型的地址。
package main import ( "fmt" ) type Cat struct { Name string Age int } func main() { //定義一個存放任意數據類型的管道 3個數據 //var allChan chan interface{} allChan := make(chan interface{}, 3) allChan<- 10 allChan<- "tom jack" cat := Cat{"小花貓", 4} allChan<- cat //我們希望獲得到管道中的第三個元素,則先將前2個推出 <-allChan <-allChan newCat := <-allChan //從管道中取出的Cat是什么? fmt.Printf("newCat=%T , newCat=%v\n", newCat, newCat) //下面的寫法是錯誤的!編譯不通過 //fmt.Printf("newCat.Name=%v", newCat.Name) //使用類型斷言 a := newCat.(Cat) fmt.Printf("newCat.Name=%v", a.Name) }
定義 interface類型的空接口,可以接收任意類型的數據,但是在取出來的時候,必須斷言!
a := newCat.(Cat)
關閉之后,不能再寫入,只能讀。
只能由發送者執行這句代碼
通道的數據接收一共有以下 4 種寫法。
1.阻塞接收數據
阻塞模式接收數據時,將接收變量作為<-操作符的左值,格式如下:
data := <-ch
執行該語句時將會阻塞,直到接收到數據并賦值給 data 變量。
2.非阻塞接收數據(有問題啊,還是會報錯deadlock)
使用非阻塞方式從通道接收數據時,語句不會發生阻塞,格式如下:
data, ok := <-ch
data:表示接收到的數據。未接收到數據時,data 為通道類型的零值。
ok:表示是否接收到數據。
非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要實現接收超時檢測,可以配合 select 和計時器 channel進行,可以參見后面的內容。
3.接收任意數據,忽略接收的數據
阻塞接收數據后,忽略從通道返回的數據,格式如下:
<-ch
執行該語句時將會發生阻塞,直到接收到數據,但接收到的數據會被忽略。這個方式實際上只是通過通道在 goroutine 間阻塞收發實現并發同步。
使用通道做并發同步的寫法,可以參考下面的例子:
package main import ( "fmt" ) func main() { // 構建一個通道 ch := make(chan int) // 開啟一個并發匿名函數 go func() { fmt.Println("start goroutine") // 通過通道通知main的goroutine ch <- 0 fmt.Println("exit goroutine") }() fmt.Println("wait goroutine") // 等待匿名goroutine <-ch fmt.Println("all done") }
4.循環接收
通道的數據接收可以借用 for range 語句進行多個元素的接收操作,格式如下:
for data := range ch { }
通道 ch 是可以進行遍歷的,遍歷的結果就是接收到的數據。數據類型就是通道的數據類型。通過 for 遍歷獲得的變量只有一個,即上面例子中的 data。
package main import ( "fmt" ) func main() { intChan := make(chan int, 3) intChan<- 100 intChan<- 200 close(intChan) // close //這時不能夠再寫入數到channel //intChan<- 300 fmt.Println("okook~") //當管道關閉后,讀取數據是可以的 n1 := <-intChan fmt.Println("n1=", n1) //遍歷管道 intChan2 := make(chan int, 100) for i := 0; i < 100; i++ { intChan2<- i * 2 //放入100個數據到管道 } //遍歷管道不能使用普通的 for 循環 // for i := 0; i < len(intChan2); i++ { // } //在遍歷時,如果channel沒有關閉,則會出現deadlock的錯誤 //在遍歷時,如果channel已經關閉,則會正常遍歷數據,遍歷完后,就會退出遍歷 close(intChan2) for v := range intChan2 { //沒有下標 fmt.Println("v=", v) } }
在遍歷管道之前要先關閉管道,不然會出現deadlock的錯誤
應用1
開兩個管道;
當寫協程完成工作之后,close數據管道,讀協程對數據管道 intChan的數據讀完之后,就向退出管道 exitChan 寫入一個 true,close掉;
主線程循環檢測退出管道里是否有數據,如果有,說明讀協程完成,主程序就可以退出了。
package main import ( "fmt" ) //write Data func writeData(intChan chan int) { for i := 1; i <= 50; i++ { //放入數據 intChan <- i // fmt.Println("writeData ", i) //time.Sleep(time.Second) } close(intChan) //關閉 } //read data func readData(intChan chan int, exitChan chan bool) { for { v, ok := <-intChan if !ok { break } // time.Sleep(time.Second) fmt.Printf("readData 讀到數據=%v\n", v) } //readData 讀取完數據后,即任務完成 exitChan <- true close(exitChan) } func main() { //創建兩個管道 intChan := make(chan int, 10) exitChan := make(chan bool, 1) go writeData(intChan) go readData(intChan, exitChan) //time.Sleep(time.Second * 10) for { _, ok := <-exitChan if !ok { break } } }
應用2
定義三個管道:
intChan :放8000個數
primeChan:放素數
exitChan :4個協程運行完畢的標志
package main import ( "fmt" "time" ) //向 intChan放入 1-8000個數 func putNum(intChan chan int) { for i := 1; i <= 80000; i++ { intChan <- i } //關閉intChan close(intChan) } // 從 intChan取出數據,并判斷是否為素數,如果是,就 // //放入到primeChan func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) { //使用for 循環 // var num int var flag bool // for { //time.Sleep(time.Millisecond * 10) num, ok := <-intChan //intChan 取不到.. if !ok { break } flag = true //假設是素數 //判斷num是不是素數 for i := 2; i < num; i++ { if num%i == 0 { //說明該num不是素數 flag = false break } } if flag { //將這個數就放入到primeChan primeChan <- num } } fmt.Println("有一個primeNum 協程因為取不到數據,退出") //這里我們還不能關閉 primeChan //向 exitChan 寫入true exitChan <- true } func main() { intChan := make(chan int, 1000) primeChan := make(chan int, 20000) //放入結果 //標識退出的管道 exitChan := make(chan bool, 4) // 4個 start := time.Now().Unix() //開啟一個協程,向 intChan放入 1-8000個數 go putNum(intChan) //開啟4個協程,從 intChan取出數據,并判斷是否為素數,如果是,就 //放入到primeChan for i := 0; i < 4; i++ { go primeNum(intChan, primeChan, exitChan) } //這里我們主線程,進行處理 //直接 go func() { for i := 0; i < 4; i++ { <-exitChan } end := time.Now().Unix() fmt.Println("使用協程耗時=", end-start) //當我們從exitChan 取出了4個結果,就可以放心的關閉 prprimeChan close(primeChan) }() //遍歷我們的 primeChan ,把結果取出 for { res, ok := <-primeChan if !ok { break } //將結果輸出 fmt.Printf("素數=%d\n", res) } fmt.Println("main線程退出") }
有一個primeNum 協程因為取不到數據,退出
有一個primeNum 協程因為取不到數據,退出
有一個primeNum 協程因為取不到數據,退出
有一個primeNum 協程因為取不到數據,退出
使用協程耗時= 3
main線程退出
存數字和計算素數比較簡單,不提
開啟4個協程,運算素數,效率比單個線程高幾倍!
go func() { for i := 0; i < 4; i++ { <-exitChan } end := time.Now().Unix() fmt.Println("使用協程耗時=", end-start) //當我們從exitChan 取出了4個結果,就可以放心的關閉 prprimeChan close(primeChan) }()
這里定義了一個匿名協程,作用是檢測4個協程 有沒有完成運行,取不出來就會阻塞,等待協程完成。也可以這樣:
if len(exitChan) == 4
關于“Go中的channel怎么聲明和使用”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Go中的channel怎么聲明和使用”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。