Golang的Fork/Join实现

news2024/11/17 8:48:26

做过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的上下文等问题,估计会让代码比较复杂,眼下这个版本足够简单,也能满足绝大多数场景了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160464.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA图像处理HLS实现RGB转灰度,提供HLS工程和vivado工程源码

目录一、图像RGB转灰度原理二、HLS方案实现三、HLS在线仿真并导出IP四、Kintex7开发板vivado工程验证五、zynq7100开发板vivado工程验证六、板级调试验证七、福利&#xff1a;工程源码获取一、图像RGB转灰度原理 图像rgb转灰度图有固定的公式&#xff0c;具体公式csdn一大堆&a…

mirco:bit是什么?小学生拿着它就能召唤神龙?

mirco:bit是什么&#xff1f;micro:bit是一款由英国广播电视公司(BBC) 为青少年编程教育设计&#xff0c;并由微软&#xff0c;三星&#xff0c;ARM&#xff0c;英国兰卡斯特大学等合作伙伴共同完成开发的微型电脑。BBC希望通过micro:bit驱动青少年参与到创造性的硬件制作和软件…

MySQL基础——DCL语句

概述 DCL(Data Control Language)语句&#xff1a;数据控制语句&#xff0c;用于控制不同数据段直接的许可和访问级别的语句。这些语句定义了数据库、表、字段、用户的访问权限和安全级别。 管理用户 查询 查询用户代码如下&#xff1a; USE mysql; SELECT * FROM user; …

ASEMI桥式整流器KBU808的优缺点

编辑-Z 型号&#xff1a;KBU808 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;800V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;560V 最大直流阻断电压&#xff08;VDC&#xff09;&#xff1a;800V 最大平均正向整流输出电流&#xf…

3D视觉技术登上火星?NASA也用上了NeRF技术做太空勘探

原文链接&#xff1a;https://www.techbeat.net/article-info?id4468 作者&#xff1a;seven_ 现阶段&#xff0c;人类探索宇宙的一个关键方向是如何高效的利用航天器返回的数据来了解和分析外太空的环境特点。其中最为常用的就是图像数据&#xff0c;但是这些数据非常宝贵&am…

C语言-自定义类型-结构体应用-通讯录(11.2)

目录 1.通讯录的设计思路 1.1主函数与通讯录框架 1.2菜单的实现 1.3通讯录的定义与初始化 2.通讯录具体功能的实现 2.1添加联系人 2.2删除联系人 2.3查找联系人 2.4修改联系人信息 2.5整理通讯录&#xff08;按年龄排序&#xff09; 2.6查看整个通讯录 3.通讯录源码…

Ubuntu 网络管理

一&#xff1a;NetPlan配置 1、安装netplan 如果/etc/netplan目录不存在请用以下命令安装&#xff1a; apt -y install netplan.io 2、配置文件 创建并编辑/etc/netplan/01-netplan.yaml文件&#xff1a; eth0&#xff1a;动态分配&#xff1b;eth1&#xff1a;静态分配 …

聊一聊nginx中KeepAlive的设置

文章目录问题分析为什么要有KeepAlive&#xff1f;TCP KeepAlive和HTTP的Keep-Alive是一样的吗&#xff1f;Nginx的TCP KeepAlive如何设置Apache中KeepAlive和KeepAliveTimeOut参考资料问题 之前工作中遇到一个KeepAlive的问题&#xff0c;现在把它记录下来&#xff0c;场景是…

【BP靶场portswigger-客户端11】跨站点脚本XSS-20个实验(上)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

ssm权限管理系统1

先说一个声明&#xff0c;这个文章可能不完整&#xff0c;也就是说&#xff0c;我只是列举出了项目里面部分疑问难点&#xff0c;然后你们想做&#xff0c;可以去b站查看这个项目的视频。我这里会上传每一部分源代码 svn: 安装好svn服务器之后&#xff0c;我们需要去服务器建…

Python之勒让德多项式

文章目录勒让德多项式简介求导和积分求根和反演拟合勒让德多项式简介 Legendre多项式是一种非常重要的正交多项式&#xff0c;在物理学中有着广泛的应用&#xff0c;例如点电荷在空间中的激发电势就具备勒让德多项式的形式。其表达形式为 Pn(x)12nn!dndxn{(x2−1)n}P_n(x)\fra…

D. Rorororobot(线段树寻找区间最大值)

Problem - 1709D - Codeforces 有一个网格&#xff0c;由n行和m列组成。行的编号是从1到n&#xff0c;从下到上。列从左至右编号为1至m。第i列的底部ai单元被封锁&#xff08;第1,2,...,ai行的单元&#xff09;&#xff0c;其余n-ai单元没有被封锁。 一个机器人正在穿越这个网…

制冷设备远程监控解决方案

大中型工业制冷设备方面在国内发展还是处在一个发展期&#xff0c;前景广阔&#xff0c;在船舶行业&#xff0c;大型化&#xff0c;个性化&#xff0c;大冷量化是趋势&#xff0c;在石油石化领域&#xff0c;工艺分离、结晶浓缩、提纯催化需求低温&#xff0c;而在建筑领域&…

Vue+element 实现影响榜功能

目录 一、英雄榜实现 1、引入element Lib 和VUE 2、设置滚动条动态显示 1&#xff09;设置对应的VUE参数名&#xff1a;scrollHiddenVar 2&#xff09;VUE data定义 3&#xff09;mounted 设置鼠标监听事件 4&#xff09;监听方法 3、设置element Table 4、table 数组…

CentOS 7 升级 GCC/G++ 版本

CentOS 7官方源的 GCC 最新版本是 4.8.5&#xff0c;该版本发布于2015年&#xff0c;年代久远且不支持高版本的 C 编译了&#xff0c;因此有必要安装高版本的 GCC。   红帽&#xff08;RedHat&#xff09;中拥有多个已经编译好了的高版本 GCC&#xff0c;但未更新到 base 和 …

再说多线程(四)——Semaphore类

Semaphore提供了更精细化的多线程控制&#xff0c;如果你看过上一节的Mutex介绍&#xff0c;那么你应该很容易理解Semaphore类。我们直接先以例子开头&#xff0c;然后在介绍这个类。1.Semaphore实例看下面的代码&#xff1a;using System; using System.Threading;namespace S…

【爪洼岛冒险记】第3站:任务1:学会int和String之间的转换;任务2:将Java运算符全部收入囊中~ 任务3:拿下Java中的类型提升

&#x1f331;博主简介&#xff1a;是瑶瑶子啦&#xff0c;一名大一计科生&#xff0c;目前在努力学习C进阶,JavaSE。热爱写博客~正在努力成为一个厉害的开发程序媛&#xff01; &#x1f4dc;所属专栏&#xff1a;爪洼岛冒险记 ✈往期博文回顾:【爪洼岛冒险记】第2站&#xff…

Allegro172版本如何快速打开和关闭层面操作指导

Allegro172版本如何快速打开和关闭层面操作指导 在做PCB设计的时候,打开和关闭某个层面是非常频繁的操作,尤其是丝印等等层面。 Allgeo升级到了172版本的时候,可以将常用的层面添加到Visibility菜单里,就不需要频繁打开颜色管理器打卡和关闭层面了,如下图 具体操作如下 打…

常见锁策略,CAS,synchronized原理

1.常见锁策略锁策略不仅仅局限于java,任何与"锁"相关的话题(操作系统,数据库...),都会涉及到锁策略,这些策略是给锁的实现者用来参考的1.1乐观锁vs悲观锁这个不是两把具体的锁.而是两类锁,是在锁冲突的概率上进行区分的乐观锁指的是预测锁竞争不是很激烈(做的工作相对…

Ambari2.7.5安装Flink1.14

文章目录下载Flink配置安装源下载ambari-flink-service服务修改配置文件创建用户和组重启Ambari登录Ambari安装Flink提交Flink任务Flink 直接单独提交到 On Yarn指定Flink在Yarn跑的容器运行Flink异常异常1异常2异常3下载Flink配置安装源 wget https://archive.apache.org/dis…