我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Golang的Fork/Join实现代码

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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实现内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Golang的Fork/Join实现代码

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Golang的Fork/Join实现代码

Fork/Join本质上是一种任务分解,将一个很大的任务分解成若干个小任务,然后再对小任务进一步分解,直到最小颗粒度,然后并发执行,对Golang的Fork/Join实现代码感兴趣的朋友跟随小编一起看看吧
2023-01-15

Golang实现单链表的示例代码

本文主要介绍了Golang实现单链表的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-03-15

golang原生实现JWT的示例代码

在Go中实现JWT验证,可以通过标准库crypto/hmac、crypto/sha256和encoding/base64来编写自己的JWT,本文就详细的来介绍一下,感兴趣的可以了解下
2023-05-19

golang实现ping命令的完整代码

这篇文章详细介绍了如何使用Go语言实现ping命令,包括导入必要的库、定义ping函数和处理用户输入。ping函数利用ICMP协议发送回显请求并解析响应,计算往返时间。该代码可用于检查主机连接情况并测量网络延迟。
golang实现ping命令的完整代码
2024-04-02

MySQL中join查询实例代码分析

这篇“MySQL中join查询实例代码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“MySQL中join查询实例代码分析
2022-11-30

golang动态执行代码怎么实现

在Go语言中,可以使用reflect包来实现动态执行代码。下面是一个简单的示例:package mainimport ("fmt""reflect")func main() {code := `package mainimport "f
2023-10-26

golang代码混淆加密怎么实现

在 Go 语言中,代码混淆和加密是通过不同的技术实现的。代码混淆是通过对代码进行变换和重组,使得代码难以被理解和分析。加密是通过对代码进行加密,使得只有授权的用户才能解密并执行。下面分别介绍代码混淆和加密的实现方法:代码混淆:使用代码压缩
2023-10-27

Golang函数重试机制实现代码

Go中函数重试机制的实现在Go中,可通过错误处理和延迟机制实现函数重试。该机制包含自定义错误类型、函数内错误处理和延迟设置。代码示例展示了处理重试错误并设置延迟时间的实现。其他考虑因素包括重试次数限制、重试规则、错误记录和并发控制。该机制可根据需求定制,以满足不同场景。
Golang函数重试机制实现代码
2024-04-23

golang观察者模式实现的代码怎么写

在Go语言中实现观察者模式,可以按照以下步骤进行:定义主题(Subject)接口,包含以下方法:type Subject interface {Register(observer Observer) // 注册观察者Unregiste
2023-10-23

泛型在golang中实现代码复用的技巧

go 中的泛型实现代码复用,通过使用类型参数创建可重用函数和算法,从而减少冗余和提高代码可维护性。泛型函数允许比较不同类型(如 int 和 string)的最大值,并使用可重用算法验证元素是否存在于切片中,为代码复用和灵活性提供了强大的工具
泛型在golang中实现代码复用的技巧
2024-05-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录