什麼是通道 channel?
”不要通過共享內存來通信 ,而應該通過通信來共享內存“ : 這句話什麼意思呢?
舉個例子🌰: 如果要把數據傳遞個另外一個協程, 可以先將數據封裝成對象, 然後把對象的指針放進channel 中, 那麼目標協程可以從channel中拿到對象指針, 並處理其指向的內存對象。
類似於通信的管道, 雖然有傳統共享數據同步機制,但go語言強烈建議我們使用 Channel 通道來實現goroutine 之間的通信
通道就是goroutine 之間的通道, 它可以讓goroutine之間相互通信。
通道必須傳遞自己類型的數據
剛定義的通道 默認是nil的, 可以通過 make(chan int) 來創建
上圖 證明通道傳的是 內存地址
怎麼使用 通道:讀操作 + 寫操作
data := <- a 從通道 a中取出值, 放到 data中
a <- data ; 將數據data放到 a 這個通道中
package main
import "fmt"
func main(){
var ch1 chan bool
ch1 = make(chan bool)
go func(){
for i:=0; i<4; i++{
fmt.Printf("子goroutine 中 i-> %d\n",i)
}
// 循環結束後 ,向通道中寫入數據
ch1 <- true// 該操作可以解除讀操作的阻塞
fmt.Println("子goroutine 結束...")
}()
data := <- ch1//先阻塞, 等子gorooutine寫入數據後, 該讀操作隨機解除阻塞
fmt.Println("main goroutine read data :",data)
fmt.Println("main over...")
}
通道 channel 的注意點:
1 用於協程 ,傳遞消息
2 每個通道都有自己的數據類型 , nil通道,不能直接存儲(即先定義,再創建)
3 使用通道傳輸數據:<-
chan <- data 發送數據到通道,向通道中寫數據,
data<-chan 從通道中獲取數據
4 阻塞: (必須 有讀也有寫, 阻塞是同時相互解除的)
發送數據: chan<-data寫阻塞的,直到另一個協程讀取數據纔會解除阻塞,
讀取數據: data:= <-chan 也是阻塞的, 直到另一端寫出數據解除阻塞
5 本身 通道是同步的, 意味着 同一時間,只能有一條通道來操作。
6 通道是 協程之間的連接, 所以通道的發送和接收必須在不同的協程中。
死鎖 : 沒有讀通道操作, 或者沒有寫通道操作 ,就會形成死鎖
如何 關閉 通道:在子協程中關閉通道
package main
import (
"fmt"
"time"
)
func main(){
/*
關閉通道
*/
ch2 := make(chan int)
go func(){
for i:=0; i<10; i++ {
ch2 <- i
}
fmt.Println("子goroutine 結束...")
close(ch2)
}()
for {
time.Sleep(2 * time.Millisecond)// 保證 數據被寫入通道
data2, ok := <-ch2
if ok != true {
fmt.Println("讀取通道數據結束...")
break
}
fmt.Println("main goroutine read data ->", data2)
}
fmt.Println("main goroutine over...")
}
對比發現 ,協程直接 也可能因爲時間片原因進行搶佔調度執行
緩衝通道
不帶緩衝的通道: 只有讀/寫 會發生阻塞
帶緩衝的通道: 數據先發送到緩衝區: 只有緩衝區滿了 或者空了纔會發生阻塞。
創建緩衝通道 只需要在後邊 加上通道大小就可以了
通道都是按照隊列的數據結構讀取的
定向通道:單向通道
ch1 := make(<- chan int) 只能讀不能寫的通道
ch2 :=make(chan <- int) 只能寫 , 不能讀的通道
ch3 : =make(chan int) 雙向 不帶緩衝通道
ch4 : =make(chan int, 4) 雙向 帶緩衝通道
你可能會問: 既然 只是單向通道:那麼又有什麼暖用?
是的: 但向通道往往只是作爲參數傳遞的 ,目的就是爲了保護而已。
因爲通道本來就是用於兩個協程之間的通信的, 設置成單向的,往往用處不大
time包下與 通道相關的函數
主要就是 timer定時器, 可以讓用戶自己定義超時邏輯, 尤其是在 select 處理多個channel 的超時、單channel讀寫的超時等情況時尤爲方便。
Timer是一次性的時間觸發事件。Ticker 是按一定的時間間隔 持續觸發事件的。
Timer 的觸發方式:
一 : t := timer.NewTimer(d)
t := timer.AfterFunc(d,F)
c:=timer.After(d)
可以看到 C是一個只寫通道,
package main
import (
"fmt"
"time"
)
func main(){
/*
1 func NewTimer(d Duration)創建一個計時器, d時間以後觸發
*/
timer := time.NewTimer(3 * time.Second) //timer是一個time.Timer指針
fmt.Printf("timer type is %T\n",timer)
fmt.Println(time.Now())
//阻塞三秒鐘 觸發 計時器
ch2 := timer.C //C 是一個通道
fmt.Println(<-ch2)// 讀取 ch2內的數據 : 讀取的是3秒之後的時間
}
flag :=timer.strop() // 停止定時器
After(d Duration)(chan) // 作用和NewTimer一樣 ,但他返回一個通道, 存儲當前時間 + d 的時間。
package main
import (
"fmt"
"time"
)
func main(){
/*
1 func NewTimer(d Duration)創建一個計時器, d時間以後觸發
*/
timer := time.NewTimer(3 * time.Second) //timer是一個time.Timer指針
fmt.Printf("timer type is %T\n",timer)
fmt.Println(time.Now())
//阻塞三秒鐘 觸發 計時器
ch2 := timer.C //C 是一個通道
fmt.Println(<-ch2)// 讀取 ch2內的數據 : 讀取的是3秒之後的時間
fmt.Println()
ch4 := time.After(3 * time.Second)//After 作用和Newtimer一樣,但返回一個 通道
fmt.Println(time.Now())
fmt.Println(<-ch4)
}
Select 語句
select 是Go語言提供的一個控制結構(選擇語句), 通過select 可以監聽在channel上的數據流動。
select和switch 語句非常像, 但是 select 會隨機執行一個可運行的case, 如果沒有case 可運行的話,有 default 會執行default, 沒有的話, select將阻塞, 直到有case 可運行
package main
import "fmt"
func main(){
/*
select和 switch 語句非常相似
有多個case 可運行會隨機運行一個,
沒有可以運行的 case 會阻塞等待,直到有case 可執行
*/
ch1 := make(chan int)
ch2 := make(chan int)
go func(){
ch1 <-10
}()
//下面我們進行 從通道ch1 ch2中讀
select {
case num1 := <-ch1 :
fmt.Println("ch1 中獲取數據->",num1)
case num2 ,ok := <-ch2:
if ok{
fmt.Println("ch2 中讀取的數據",num2)
}else{
fmt.Println("ch2 通道已經關閉...")
}
}
fmt.Println("main over ...")
}
這個時候 select 就進行隨機選擇case執行了
package main
import (
"fmt"
"time"
)
func main(){
/*
select和 switch 語句非常相似
有多個case 可運行會隨機運行一個,
沒有可以運行的 case 會阻塞等待,直到有case 可執行
*/
ch1 := make(chan int)
ch2 := make(chan int)
go func(){
time.Sleep(1* time.Second)
ch1 <-10
}()
go func(){
time.Sleep(1 * time.Second)
ch2 <-20
}()
//下面我們進行 從通道ch1 ch2中讀
select {
case num1 := <-ch1 :
fmt.Println("ch1 中獲取數據->",num1)
case num2 ,ok := <-ch2:
if ok{
fmt.Println("ch2 中讀取的數據",num2)
}else{
fmt.Println("ch2 通道已經關閉...")
}
}
fmt.Println("main over ...")
}
經過測試; select會優先執行 已就緒的語句