Golang的Fork/Join实现代码

 更新时间:2023年01月15日 15:25:45   作者:互联网速递520  
Fork/Join本质上是一种任务分解,将一个很大的任务分解成若干个小任务,然后再对小任务进一步分解,直到最小颗粒度,然后并发执行,对Golang的Fork/Join实现代码感兴趣的朋友跟随小编一起看看吧

做过Java开发的同学肯定知道,JDK7加入的Fork/Join是一个非常优秀的设计,到了JDK8,又结合并行流中进行了优化和增强,是一个非常好的工具。

1、Fork/Join是什么

Fork/Join本质上是一种任务分解,即:将一个很大的任务分解成若干个小任务,然后再对小任务进一步分解,直到最小颗粒度,然后并发执行。

这么做的优点很明显,就是可以大幅提升计算性能,缺点嘛,也有一点,那就是资源开销要大一些。

在网上找了一张图,任务分解就是这个意思:

2、Golang中的Fork/Join实现

对于Golang中的Fork/Join的实现,我参考了JDK的源码,利用了Goroutine特性,这样就能充分利用MPG模型,不必自己再处理任务窃取等问题了,用起来还是蛮爽的。

废话不多说,请看代码:

package like_fork_join
 
import (
    "fmt"
    "github.com/oklog/ulid/v2"
)
 
const defaultPageSize = 10
 
type MyForkJoinTask struct {
    size int
}
 
// NewMyTask 初始化一个任务
func NewMyTask(pageSize int) *MyForkJoinTask {
    var size = defaultPageSize
    if pageSize > size {
        size = pageSize
    }
    return &MyForkJoinTask{
        size: size,
    }
}
 
// Do 执行任务时,传入一个切片
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)
    // 任务小于最小颗粒度时,直接执行逻辑(此处是求和),不再拆分,否则进行分治
    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)
 
        // 等待左边和右边分治子任务结束
        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
            }
        }
 
        // 取结果
        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
    }
}

代码也不复杂,有注释,大家耐心读一下就明白了。

3、测试验证

我写了一个比较有压力的测试用例代码,请看:

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)
                }
            }
        })
    }
}

测试成功:

    --- PASS: TestMyTask_Do/sum(1,10000) (1257.79s)
PASS

4、小优化

删除所有fmt包的控制台输出,再跑单元测试结果:

=== RUN   TestMyTask_Do
--- PASS: TestMyTask_Do (60.53s)
=== RUN   TestMyTask_Do/sum(1,10000)
    --- PASS: TestMyTask_Do/sum(1,10000) (60.53s)
PASS

20万次加法计算,长度为1万的数组的20次计算,60秒搞定,性能巨强,Golang就是棒!

5、后续计划

计划后续再研究研究,看能否把执行任务的逻辑做成泛型和函数闭包,给抽象出来,这样就能单独形成一个通用型的代码包,供外部各种应用程序使用了,不过考虑到goroutine的上下文等问题,估计会让代码比较复杂,眼下这个版本足够简单,也能满足绝大多数场景了。

到此这篇关于Golang的Fork/Join实现的文章就介绍到这了,更多相关Golang的Fork/Join实现内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang栈结构和后缀表达式实现计算器示例

    Golang栈结构和后缀表达式实现计算器示例

    这篇文章主要为大家介绍了Golang栈结构和后缀表达式实现计算器示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Go语言实战之详细掌握正则表达式的应用与技巧

    Go语言实战之详细掌握正则表达式的应用与技巧

    正则表达式是一种从左到右与主题字符串匹配的模式,正则表达式用于替换字符串中的文本,验证表单,基于模式匹配从字符串中提取子字符串等等,这篇文章主要给大家介绍了关于Go语言实战之详细掌握正则表达式的应用与技巧,需要的朋友可以参考下
    2023-12-12
  • 详解Go语言中如何高效遍历目录

    详解Go语言中如何高效遍历目录

    目录遍历是一个很常见的操作,它的使用场景有如文件目录查看、文件系统清理、日志分析、项目构建等,本文将详细介绍在Go中几种遍历目录文件的方法,需要的可以参考下
    2024-02-02
  • Golang使用Gin实现文件上传的示例代码

    Golang使用Gin实现文件上传的示例代码

    本文我们主要介绍了Golang如何使用Gin实现文件上传,Go标准库net/http对文件上传已经提供了非常完善的支持,而Gin框架在其基础上进一步封装,因此使用Gin开发文件上传功能时,只需要简单几行代码便可以实现,需要的朋友可以参考下
    2024-02-02
  • golang中context的作用详解

    golang中context的作用详解

    这篇文章主要介绍了golang中context的作用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Golang 发送http请求时设置header的实现

    Golang 发送http请求时设置header的实现

    这篇文章主要介绍了Golang 发送http请求时设置header的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • golang定时器和超时的使用详解

    golang定时器和超时的使用详解

    这篇文章主要介绍了golang定时器和超时的使用详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Go 语言前缀树实现敏感词检测

    Go 语言前缀树实现敏感词检测

    这篇文章主要为大家介绍了Go语言前缀树实现敏感词检测实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • 浅谈Golang的Work Stealing机制

    浅谈Golang的Work Stealing机制

    Go的运行时系统使用了一种名为Work Stealing的调度策略来分配Goroutine到可用线程上执行,本文主要介绍了浅谈Golang的Work Stealing机制,具有一定的参考价值,感兴趣的可以了解一下
    2025-03-03
  • Go语言框架快速集成限流中间件详解

    Go语言框架快速集成限流中间件详解

    这篇文章主要为大家介绍了Go语言框架快速集成限流中间件详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09

最新评论