iT邦幫忙

2021 iThome 鐵人賽

DAY 6
1
Software Development

Hey! Go Design Patterns系列 第 6

DAY 6:Feature Pattern,我把未來託付給你了!

什麼是 Future Pattern?

呼叫者將 task 交給 goroutine 執行,執行完畢後 goroutine 將 task 運行得到的結果傳回呼叫者

在昨天講解完 Thread-Per-Message Pattern 後,將 message 以 goroutine 執行後,是沒辦法獲得回傳值的,如果有此需求,可以搭配 Future Pattern

當 goroutine 運行後,呼叫者需要有一個中間物件來去取得 goroutine 未來運行的結果,golang 通常採用 channel 實作

問題情境

延續推播新聞系統的情境,將新的新聞直接推播出去,除了推播系統效率要高,還需紀錄推播完成的時間

相關的 code 在Github - go-design-patterns

昨天的 code 並沒辦法接收到PushNews()的回傳運行完結束的時間值:

package main

import (
	"fmt"
	"time"
)

func PushNews(news string, startTime time.Time) time.Time {
	time.Sleep(time.Duration(3 * time.Second)) //模擬推播運行的時間
	fmt.Printf("%s cost %s\n", news, time.Since(startTime))
	return time.Now()
}

func main() {
	start := time.Now()
	allNews := []string{
		"中秋節來了",
		"記得",
		"不要戶外烤肉~",
	}
	for _, news := range allNews {
		go PushNews(news, start)
	}
	time.Sleep(10 * time.Second) //等待goroutine執行完畢
}

解決方式

將 golang channel,實作至PushNews()中。goroutine 運行後,將newsCh{}先回傳給呼叫者main(),在**未來(feature)**時 goroutine 會將發送完畢的時間time.Now()傳至 channel,呼叫者main()只需在需要時等待 channel 收到time.Now()出現,如下:

package main

import (
	"fmt"
	"time"
)

func PushNews(news string, startTime time.Time) <-chan time.Time {
	newsCh := make(chan time.Time)
	go func() {
		time.Sleep(time.Duration(3 * time.Second)) //模擬推播運行的時間
		fmt.Printf("%s cost %s\n", news, time.Since(startTime))
		newsCh <- time.Now()
	}()
	return newsCh
}

func main() {
	start := time.Now()
	allNews := []string{
		"中秋節來了",
		"記得",
		"不要戶外烤肉~",
	}
	newsChs := []<-chan time.Time{}
	for _, news := range allNews {
		newsChs = append(newsChs, PushNews(news, start))
	}

	// do something

	for index, newsCh := range newsChs {
		fmt.Printf("news %d is sent at %s\n", index, <-newsCh)
	}
}

先透過for _, news := range allNews將所有的新聞發送以啟動 goroutine,但這邊僅是啟動,並取得一個中間物件 channel,等到有需要再從 channel 取得資料,甚至可以先在// do somethins處做其他事情。

最後在for index, newsCh := range newsChs取得 channel 的資料,如Guarded Suspension Pattern yorktodo所說,channel 會等到有資料再運行,否則等待,所以此處會等到所有<-newsCh都接收完畢才會離開 for 迴圈運行完main(),如下:

與常見的 javascript promise 對比

promise 與 feature 是相似的概念,差異是:

  • promise 是以.then(function)的風格傳送 function 給 promise 來去實作取得資料後的動作
  • feature 是以.get()或者 golang <-channel的方式取得資料後再呼叫處實作後續動作

但兩者精神相同,都是處理如何異步取得資料。

如果熟悉 javascript 的話,可以用Promise.all()去思考此範例,如下:

const pushNews = (news, startTime) =>
  new Promise((resolve) =>
    setTimeout(() => {
      console.log(`${news} cost ${Date.now() - startTime}`);
      resolve(Date.now());
    }, 3000)
  );

const start = Date.now();
Promise.all(
  ["中秋節來了", "記得", "不要戶外烤肉~"].map((news) => pushNews(news, start))
).then((allNews) =>
  allNews.map((finishTimes, index) =>
    console.log(`"news ${index} is sent at ${finishTimes}"`)
  )
);

// do something

會發現for _, news := range allNewsPromise.all()相同都是在啟動異步的 code,等到異步的資料都會來就以.then(function)中的 function 來處理,由於示異步,我們也可以在程式// do something處執行其他事情而不被 block


上一篇
DAY 5:Thread-Per-Message Pattern,預備...發射!
下一篇
DAY 7:Fan-Out Fan-In Pattern,看吧世界!這就是多人解決的力量!
系列文
Hey! Go Design Patterns30

尚未有邦友留言

立即登入留言