iT邦幫忙

1

Golang-Channel & Goroutine-進階篇

  • 分享至 

  • twitterImage
  •  

基礎篇簡單了介紹Channel&Goroutine的基本使用方法
接下來就是實際應用的問題了

Select

實際例子上可能會有1-N個不等的chaneel
這時候就需要使用專門給channel使用的switch case,叫做Select
Select有幾個地方要注意

  • 只能給channel用
  • 假設同時有兩個channel達成條件,他會隨機選擇一個case進行

語法請看綜合範例2有演示

Goroutine

使用Goroutine最重要的
不要對routing的輸出順序以及時間做出假設

舉個簡單的例子,只要印出123

func main() {
	go func() {
		log.Println("123")
	}()
	//沒有暫停1秒鐘讓另一個routing準備好,有可能程式會直接結束而不會輸出任何東西
	time.Sleep(1 * time.Second)
}

可以試試看把time.Sleep(1 * time.Second)給Remark,基本上是不會輸出任何東西,至少我是沒有成功過
因為Goroutine有極大的可能在main function執行完時而還沒有開始執行
所以Goroutine的closure就會什麼都沒有執行就結束了

Channel搭配Goroutine

這是最重要也是最難的部分
在使用Channel的時候最常要注意的事情就是deaklock
關於deaklock的情形可以參考:https://github.com/aceld/golang/blob/main/5%E3%80%81channel.md

Unbuffered Channel

基礎篇有提到UnBuffered Channel有著同步的特性
這種channel的兩端會等待著寫入與讀取,而且因為沒有Buffer,所以Channel的兩端會同步資料

func main() {
	ch := make(chan bool)
	go func() {
		ch <- true
	}()

	// 雖然沒有time.Sleep等待goroutine執行
	// 但最後有一個<-ch一直讀取channel
	// 所以可以看到Print true
	log.Println(<-ch)
}

Buffered Channel

可以使用for range方法讀取裡面的資料

func main() {
	ch := make(chan int, 10)
	go func() {
		for i := 0; i < 10; i++ {
			ch <- i
		}
		close(ch)
	}()

	for value := range ch {
		log.Println(value)
	}
}

綜合範例1 - 1個Routing + 1個channel + for loop

從channel利用for loop讀取資料,可以搭配基礎篇服用
make channel的那一行可以加上cap玩看看,馬上就能知道UnBuffered & Buffered的差異
close(ch)那段最重要,可以試試看remark的結果,保證deadlock

func main() {
	ch := make(chan int) // 可以加上cap,一樣可以正常跑程式,而且可以看出UnBuffered & Buffered的差異
	go func() {
		for i := 0; i < 10; i++ {
			log.Println("Send value", i)
			ch <- i
		}
		close(ch) // close channel很重要,沒有閉關的話,另一端一直在讀取,而沒有資料寫入,就會產生deadlock
	}()

	for {
		val, ok := <-ch
		if ok {
			log.Println("Receive value", val)
		} else {
			break
		}
	}
}

綜合範例2 - N個Routing + N個channel + for loop + select

執行這段程式碼有個重點
不要預期routing的執行時間,先後順序,一切的假設都不要有
for loop的部分是要一直讀取channel value
而當x==20時就會觸發事件s<-str
select接收到s channel的value時就會執行該case,跳出帶有LOOP tag的for loop

func main() {
	str := "stop"
	c := make(chan int)
	s := make(chan string)

	for i := 0; i < 50; i++ {
		go func(x int) {
			if x == 20 {
				log.Println("goroutine will stop")
				s <- str
			} else {
				log.Println("Send value", x)
				c <- x
			}
		}(i)
	}

LOOP:
	for {
		select {
		case v, ok := <-c:
			if ok {
				log.Println("Select value", v)
			}
		case str := <-s:
			log.Println(str)
			break LOOP
		default:
			log.Println("wait")
		}
	}
}

小結

在使用Goroutine&channel的時候要注意的地方大致可以歸類在以下幾點

  • 同步使用
  • 非同步使用
  • channel是否已經閉關
  • channel是否已滿
  • 不要對routing做出執行順序、執行時間的假設
  • 假設在不閉關channel的前提下,重複利用channel,同時又怕因為讀取造成deadlock,可以用unbuffered channel,搭配sync.WaitGroup+for select來實現

然後要自己寫一遍,多deadlock幾次到差點想把筆電一分為二的時候你就會了(誤

其他還有Package sync & Package runtime,就等下一篇吧


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言