golang入門day6 (通道 + select)

什麼是通道 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會優先執行 已就緒的語句

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章