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

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

Tags
Golang
Hey! Go Design Patterns
ithome 2021 ironman
Date
Sep 6, 2021

什麼是 Future Pattern?

呼叫者將 task 交給 goroutine 執行,執行完畢後 goroutine 將 task 運行得到的結果傳回呼叫者
在昨天講解完 Thread-Per-Message Pattern 後,將 message 以 goroutine 執行後,是沒辦法獲得回傳值的,如果有此需求,可以搭配 Future Pattern
當 goroutine 運行後,呼叫者需要有一個中間物件來去取得 goroutine 未來運行的結果,golang 通常採用 channel 實作
notion image

問題情境

延續推播新聞系統的情境,將新的新聞直接推播出去,除了推播系統效率要高,還需紀錄推播完成的時間
相關的 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(),如下:
notion image

與常見的 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