Golang的Fork/Join实现

这篇具有很好参考价值的文章主要介绍了Golang的Fork/Join实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

1、Fork/Join是什么

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

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

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

Golang的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的上下文等问题,估计会让代码比较复杂,眼下这个版本足够简单,也能满足绝大多数场景了。文章来源地址https://www.toymoban.com/news/detail-482530.html

到了这里,关于Golang的Fork/Join实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 无限分解流----Fork/Join框架

    Fork译为拆分,Join译为合并 Fork/Join框架的思路是把一个非常巨大的任务,拆分成若然的小任务,再由小任务继续拆解。直至达到一个相对合理的任务粒度。然后执行获得结果,然后将这些小任务的结果汇总,生成大任务的结果, 直至汇总成最初巨大任务的结果。如下图: 红

    2024年02月08日
    浏览(73)
  • CSS 重新认识 !important 肯定有你不知道的

    重新认识 !important 影响级联规则 与 animation 和 transition 的关系 级联层cascade layer 内联样式 !important 与权重 !important 与简写属性 !important 与自定义变量 !important 最佳实践 在开始之前, 先来规范一下文中的用语, 首先看 W3C 中关于 CSS 的一些术语定义吧. 下图来自 W3C 我们将一个完整

    2024年02月04日
    浏览(32)
  • 希望所有计算机专业同学都知道这些老师

    翁恺老师是土生土长的浙大码农,从本科到博士都毕业于浙大计算机系,后来留校教书,一教就是20多年。 翁恺老师的c语言课程非常好,讲解特别有趣,很适合初学者学习。 郝斌老师的思路是以初学者的思路来思考的,非常适合小白,你不理解的问题,基本上他都会详细说

    2024年02月05日
    浏览(34)
  • 希望计算机专业同学都知道这些博主

    “宝藏老师”、“干货满满”、“羡慕湖科大”...这些都是网友对这门网课的评价,可见网课质量之高!最全面的面试网站 湖南科技大学《计算机网络》微课堂是该校高军老师精心制作的视频课程,用简单的语言描述复杂的问题,用生动的动画演示抽象概念,更加便于学生理

    2024年02月05日
    浏览(38)
  • 从源码角度剖析 golang 如何fork一个进程

    创建一个新进程分为两个步骤,一个是fork系统调用,一个是execve 系统调用,fork调用会复用父进程的堆栈,而execve直接覆盖当前进程的堆栈,并且将下一条执行指令指向新的可执行文件。 在分析源码之前,我们先来看看golang fork一个子进程该如何写。(👉严格的讲是先fork再

    2024年02月07日
    浏览(37)
  • java中多线程去跑海量数据使用线程池批量ThreadPoolExecutor处理的方式和使用Fork/Join框架的方式那种效率高?

    在Java中,使用线程池(ThreadPoolExecutor)和使用Fork/Join框架来处理海量数据的效率取决于具体的应用场景和需求。下面是一些需要考虑的因素: 任务类型:如果任务是CPU密集型的,那么使用Fork/Join框架可能更高效,因为它可以自动进行任务分割和并行处理。如果任务是I/O密集

    2024年02月10日
    浏览(38)
  • 你在项目中做过哪些安全防范措施?,解密前端开发常见误区

    XSS 攻击 按照之前说的思路,先讲概念,说用途 什么是XSS攻击 XSS即 Cross Site Scripting (跨站脚本攻击),指的是攻击者想尽一切办法将一些可执行的代码注入到网页中,利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。 为了不和层叠样

    2024年04月10日
    浏览(71)
  • golang实现webgis后端开发

    目录 前言 二、实现步骤 1.postgis数据库和model的绑定 2.将pg库中的要素转换为geojson (1)几何定义 (2)将wkb解析为几何类型 (3)定义geojson类型 (4)数据转换 (5)数据返回  2.前端传入的geojson储存到数据库 3、其他功能实现 总结         停更了接近一个月都在研究一门新语言gola

    2024年02月08日
    浏览(40)
  • 使用Fork和GitHub完成团队项目开发

    拥有自己的GitHub账号 项目组组长已经创建好了 一个远程仓库 能够科学上网 安装了Fork软件   该内容分为两个部分,分别为团队协作者(项目成员)和团队组织者(项目组长),我们首先来介绍作为项目成员该如何完成团队项目的开发。 在你的电脑上任意地方(只要你找的

    2024年02月01日
    浏览(30)
  • 大学校友会管理APP系统开发 重温同学梦再叙校园情

           互联网技术的深入发展,让各行各业对网络的依赖都逐渐加深,可以说网络在今天已经成为无数个你我他不可或缺的平台。学生时代是一生中最美好的时期,校友是社会高效重要的社会资本和无形资产,校友与校友之间信息交流也需要依靠互联网,所以校友会APP小程

    2024年02月03日
    浏览(85)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包