Golang的Fork/Join實(shí)現(xiàn)代碼
做過(guò)Java開(kāi)發(fā)的同學(xué)肯定知道,JDK7加入的Fork/Join是一個(gè)非常優(yōu)秀的設(shè)計(jì),到了JDK8,又結(jié)合并行流中進(jìn)行了優(yōu)化和增強(qiáng),是一個(gè)非常好的工具。
1、Fork/Join是什么
Fork/Join本質(zhì)上是一種任務(wù)分解,即:將一個(gè)很大的任務(wù)分解成若干個(gè)小任務(wù),然后再對(duì)小任務(wù)進(jìn)一步分解,直到最小顆粒度,然后并發(fā)執(zhí)行。
這么做的優(yōu)點(diǎn)很明顯,就是可以大幅提升計(jì)算性能,缺點(diǎn)嘛,也有一點(diǎn),那就是資源開(kāi)銷要大一些。
在網(wǎng)上找了一張圖,任務(wù)分解就是這個(gè)意思:
2、Golang中的Fork/Join實(shí)現(xiàn)
對(duì)于Golang中的Fork/Join的實(shí)現(xiàn),我參考了JDK的源碼,利用了Goroutine特性,這樣就能充分利用MPG模型,不必自己再處理任務(wù)竊取等問(wèn)題了,用起來(lái)還是蠻爽的。
廢話不多說(shuō),請(qǐng)看代碼:
package like_fork_join import ( "fmt" "github.com/oklog/ulid/v2" ) const defaultPageSize = 10 type MyForkJoinTask struct { size int } // NewMyTask 初始化一個(gè)任務(wù) func NewMyTask(pageSize int) *MyForkJoinTask { var size = defaultPageSize if pageSize > size { size = pageSize } return &MyForkJoinTask{ size: size, } } // Do 執(zhí)行任務(wù)時(shí),傳入一個(gè)切片 func (t *MyForkJoinTask) Do(numbers []int) int { JoinCh := make(chan bool, 1) resultCh := make(chan int, 1) t.do(numbers, JoinCh, resultCh, ulid.Make().String()) result := <-resultCh return result } func (t *MyForkJoinTask) do(numbers []int, joinCh chan bool, resultCh chan int, id string) { defer func() { joinCh <- true close(joinCh) close(resultCh) }() fmt.Printf("id %s numbers %+v\n", id, numbers) // 任務(wù)小于最小顆粒度時(shí),直接執(zhí)行邏輯(此處是求和),不再拆分,否則進(jìn)行分治 if len(numbers) <= t.size { var sum = 0 for _, number := range numbers { sum += number } resultCh <- sum fmt.Printf("id %s numbers %+v, result %+v\n", id, numbers, sum) return } else { start := 0 end := len(numbers) middle := (start + end) / 2 // 左 leftJoinCh := make(chan bool, 1) leftResultCh := make(chan int, 1) leftId := ulid.Make().String() go t.do(numbers[start:middle], leftJoinCh, leftResultCh, id+"->left->"+leftId) // 右 rightJoinCh := make(chan bool, 1) rightResultCh := make(chan int, 1) rightId := ulid.Make().String() go t.do(numbers[middle:], rightJoinCh, rightResultCh, id+"->right->"+rightId) // 等待左邊和右邊分治子任務(wù)結(jié)束 var leftDone, rightDone = false, false for { select { case _, ok := <-leftJoinCh: if ok { fmt.Printf("left %s join done\n", leftId) leftDone = true } case _, ok := <-rightJoinCh: if ok { fmt.Printf("right %s join done\n", rightId) rightDone = true } } if leftDone && rightDone { break } } // 取結(jié)果 var ( left = 0 right = 0 leftResultDone = false rightResultDone = false ) for { select { case l, ok := <-leftResultCh: if ok { fmt.Printf("id %s numbers %+v, left %s return: %+v\n", id, numbers, leftId, left) left = l leftResultDone = true } case r, ok := <-rightResultCh: if ok { fmt.Printf("id %s numbers %+v, right %s return: %+v\n", id, numbers, rightId, right) right = r rightResultDone = true } } if leftResultDone && rightResultDone { break } } resultCh <- left + right return } }
代碼也不復(fù)雜,有注釋,大家耐心讀一下就明白了。
3、測(cè)試驗(yàn)證
我寫了一個(gè)比較有壓力的測(cè)試用例代碼,請(qǐng)看:
package like_fork_join import ( "fmt" "testing" ) func TestMyTask_Do(t1 *testing.T) { type args struct { numbers []int } const max = 10000 var nums = make([]int, 0, max) var want = 0 for i := 1; i <= max; i++ { nums = append(nums, i) want += i } tests := []struct { name string args args want int }{ {name: fmt.Sprintf("sum(1,%d)", max), args: args{numbers: nums}, want: want}, } for _, tt := range tests { t1.Run(tt.name, func(t1 *testing.T) { for i := 0; i <= 100; i += 5 { t := NewMyTask(i) if got := t.Do(tt.args.numbers); got != tt.want { t1.Errorf("Do() = %v, want %v", got, tt.want) } } }) } }
測(cè)試成功:
--- PASS: TestMyTask_Do/sum(1,10000) (1257.79s) PASS
4、小優(yōu)化
刪除所有fmt包的控制臺(tái)輸出,再跑單元測(cè)試結(jié)果:
=== RUN TestMyTask_Do
--- PASS: TestMyTask_Do (60.53s)
=== RUN TestMyTask_Do/sum(1,10000)
--- PASS: TestMyTask_Do/sum(1,10000) (60.53s)
PASS
20萬(wàn)次加法計(jì)算,長(zhǎng)度為1萬(wàn)的數(shù)組的20次計(jì)算,60秒搞定,性能巨強(qiáng),Golang就是棒!
5、后續(xù)計(jì)劃
計(jì)劃后續(xù)再研究研究,看能否把執(zhí)行任務(wù)的邏輯做成泛型和函數(shù)閉包,給抽象出來(lái),這樣就能單獨(dú)形成一個(gè)通用型的代碼包,供外部各種應(yīng)用程序使用了,不過(guò)考慮到goroutine的上下文等問(wèn)題,估計(jì)會(huì)讓代碼比較復(fù)雜,眼下這個(gè)版本足夠簡(jiǎn)單,也能滿足絕大多數(shù)場(chǎng)景了。
到此這篇關(guān)于Golang的Fork/Join實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang的Fork/Join實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang使用Gin框架實(shí)現(xiàn)HTTP響應(yīng)格式統(tǒng)一處理
在gin框架中,我們可以定義一個(gè)中間件來(lái)處理統(tǒng)一的HTTP響應(yīng)格式,本文主要為大家介紹了具體是怎么定義實(shí)現(xiàn)這樣的中間件的,感興趣的小伙伴可以了解一下2023-07-07Golang try catch與錯(cuò)誤處理的實(shí)現(xiàn)
社區(qū)不少人在談?wù)?nbsp;golang 為毛不用try/catch模式,而采用苛刻的recovery、panic、defer組合,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2021-07-07Go使用sync.Map來(lái)解決map的并發(fā)操作問(wèn)題
在 Golang 中 map 不是并發(fā)安全的,sync.Map 的引入確實(shí)解決了 map 的并發(fā)安全問(wèn)題,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解一下2021-10-10使用Golang調(diào)用攝像頭并進(jìn)行圖像處理
近年來(lái),攝像頭成為了我們生活中不可或缺的設(shè)備之一,從智能手機(jī)到安全監(jiān)控系統(tǒng),無(wú)處不在的攝像頭給我們帶來(lái)了便利和安全,在開(kāi)發(fā)攝像頭相關(guān)的應(yīng)用程序時(shí),選擇一種高效和易用的編程語(yǔ)言是非常重要的,本文將介紹如何使用Golang調(diào)用攝像頭并進(jìn)行圖像處理2023-11-11gin正確多次讀取http?request?body內(nèi)容實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了gin正確多次讀取http?request?body內(nèi)容實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01基于Go語(yǔ)言實(shí)現(xiàn)類似tree命令的小程序
tree?命令是一個(gè)小型的跨平臺(tái)命令行程序,用于遞歸地以樹(shù)狀格式列出或顯示目錄的內(nèi)容。本文將通過(guò)Go語(yǔ)言實(shí)現(xiàn)類似tree命令的小程序,需要的可以參考一下2022-10-10golang如何實(shí)現(xiàn)proxy代理簡(jiǎn)單方法
這篇文章主要給大家介紹了關(guān)于golang如何實(shí)現(xiàn)proxy代理簡(jiǎn)單方法的相關(guān)資料,Proxy是golang實(shí)現(xiàn)的高性能http,https,websocket,tcp,udp,socks5,ss代理服務(wù)器,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10