Go例程與無緩衝channel

基本語法

如下語句創建一個新的go例程,該例程的執行入口爲routine1,該語句執行後立即返回:

func routine1() {}
go routine1()

channel是go例程之間通信的方式之一,定義一個channel的語法如下,其中的chantype可以是基本數據類型(如本例的int),或自定義類型:

type chantype int
c := make(chan chantype)

實例:蛋糕店

一個蛋糕的生產需要分別經歷烘焙冷卻雕花三個步驟,在蛋糕店的流水線上相應地有烘焙師冷卻工雕花師三個角色,假設現在的生產任務是3塊蛋糕,那麼烘焙師完成烘焙後可以將半成品放到產線上,自己繼續烘焙第二塊蛋糕。冷卻工從產線上取出半成品進行冷卻,冷卻完成後放到產線上,轉而繼續冷卻第二塊蛋糕。雕花師此時就可以從產線上取出冷卻後的蛋糕進行雕花了。除了第一個蛋糕的生產過程冷卻工和雕花師需要等待外,後續流程,三個角色的工作都可以並行進行了。

蛋糕店的定義如下:

type Shop struct {
	cakes        int           // quantity of cakes to be made
	bakeTime     time.Duration // time to bake
	iceTime      time.Duration // time to ice
	inscribeTime time.Duration // time to inscribe
}

烘焙蛋糕

type cake int

func (s Shop) bake(baked chan cake) {
	defer close(baked)

	for i := 0; i < s.cakes; i++ {
		c := cake(i)

		fmt.Println("baking", i)
		work(s.bakeTime)
		fmt.Println(i, "baked")

		baked <- c
	}
}

func work(duration time.Duration) {
	time.Sleep(duration)
}

烘焙師每完成一個蛋糕的烘焙就把半成品放到產線上,其中的work模擬各個角色的工作。baked是一個無緩衝的channel,也就是說烘焙師一次只能投放一個蛋糕,即使第二個蛋糕烘焙完成,也必須等待冷卻工從產線上取走蛋糕他才能繼續投放。

上述代碼中的type cake int定義是爲了更好地表達本例的語義,即投放到產線上的是蛋糕,而不是一個沒有含義的int

冷卻蛋糕

func (s Shop) ice(iced chan cake, baked chan cake) {
	defer close(iced)

	for c := range baked {
		fmt.Println("icing", c)
		work(s.iceTime)
		fmt.Println(c, "iced")

		iced <- c
	}
}

注意這裏for循環的區別,冷卻工是從產線上(baked)取蛋糕,而不是像烘焙師那樣從零開始。對一個無緩衝channel做range會阻塞當前例程,直到有其它例程向該channel發送了數據,或者該channel被close。所以你看到bake裏面烘焙師完成所有蛋糕的烘焙後會執行close(baked),以通知冷卻工今天的烘焙半成品就這麼多,你不要繼續再等了。於是冷卻工在從baked內取完所有蛋糕後也就可以結束自己的工作了。

雕花

func (s Shop) inscribe(iced chan cake) {
	for c := range iced {
		fmt.Println("inscribing", c)
		work(s.inscribeTime)
		fmt.Println(c, "finished")
	}
}

雕花師的工作與冷卻工類似,從產線的iced上取出已被冷卻的蛋糕進行雕花,同樣的,冷卻工在完成所有蛋糕的冷卻後需要顯式close(iced),以通知雕花師今天的生產任務就這麼多,不要再等了。

創建生產線

func (s Shop) Work() {
	baked := make(chan cake)
	iced := make(chan cake)
	
	go s.bake(baked)
	go s.ice(iced, baked)
	s.inscribe(iced)
}

生產線創建,我們先在烘焙師與冷卻工之間放置一個工作臺baked,以備烘焙師烘焙完成後將半成品放置上去,注意,這個工作臺是無緩衝的,也就是一次只能放置一個蛋糕。然後在冷卻工與雕花師之間放置一個用以存放冷卻後的蛋糕的工作臺iced,以備冷卻工放置蛋糕供雕花師取用,同樣,這個工作臺也是無緩衝的。

我們使用了三個例程來模擬三個角色的工作:

  • s.bake:烘焙師例程
  • s.ice:冷卻工例程
  • s.inscribe:雕花師例程,也就是主例程

啓動生產線

func main() {
	shop := Shop{
		cakes:        3,
		bakeTime:     1 * time.Second,
		iceTime:      2 * time.Second,
		inscribeTime: 3 * time.Second,
	}

	shop.Work()
}

今天的生產任務是3個蛋糕,其中烘焙師烘焙一個蛋糕需要1秒,冷卻工冷卻一個蛋糕需要2秒,雕花師雕刻一個蛋糕需要3秒。執行效果如下:

baking 0 # 第一個蛋糕在烘焙時,冷卻工和雕花師是被阻塞的

0 baked # 第一個蛋糕烘焙完成
baking 1 # 烘焙師繼續烘焙第二個
icing 0 # 與此同時,冷卻工開始冷卻第一個蛋糕

1 baked # 烘焙師完成了第二個蛋糕的烘焙,但是由於冷卻工還沒有完成第一個蛋糕的冷卻,因此烘焙師被阻塞了

0 iced # 冷卻工終於完成了第一個蛋糕的冷卻
icing 1 # 開始冷卻第二個蛋糕
baking 2 # 於是烘焙師也可以投放第二個蛋糕,開始烘焙第三個了
inscribing 0 # 與此同時,雕花師也可以開始雕刻冷卻工投放到產線的第一個蛋糕了

2 baked # 烘焙師效率非常高,第三個蛋糕烘焙完成,它關閉了baked,通知冷卻工這是最後一個了

1 iced # 冷卻工完成了第二個蛋糕的冷卻,但是由於雕花師工作比較耗時,第一個蛋糕還沒雕刻完成,因此他必須等待

0 finished # 第一個蛋糕出爐
inscribing 1 # 雕刻師開始雕刻第二個蛋糕
icing 2 # 於是冷卻工可以開始進行最後一個蛋糕的冷卻了

2 iced # 冷卻工收工,關閉iced,通知雕花師這是最後一個蛋糕

1 finished # 第二個蛋糕出爐
inscribing 2 # 雕花師進行最後一個蛋糕的雕刻

2 finished # 第三個蛋糕雕刻完成,今天的生產任務圓滿完成

完整源碼

蛋糕店

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