Java开发者的Golang进修指南:从0->1带你实现协程池

这篇具有很好参考价值的文章主要介绍了Java开发者的Golang进修指南:从0->1带你实现协程池。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在Java编程中,为了降低开销和优化程序的效率,我们常常使用线程池来管理线程的创建和销毁,并尽量复用已创建的对象。这样做不仅可以提高程序的运行效率,还能减少垃圾回收器对对象的回收次数。

在Golang中,我们知道协程(goroutine)由于其体积小且效率高,在高并发场景中扮演着重要的角色。然而,资源始终是有限的,即使你的内存很大,也会有一个限度。因此,协程池在某些情况下肯定也会有其独特的适用场景。毕竟,世上并不存在所谓的银弹,只有在特定情况下才能找到最优的解决方案。因此,在Golang中,我们仍然需要考虑使用协程池的情况,并根据具体场景来选择最佳的解决方案。

今天,我们将从Java线程池的角度出发,手把手地带你实现一个Golang协程池。如果你觉得有些困难,记住我们的宗旨是使用固定数量的协程来处理所有的任务。这样做的好处是可以避免协程数量过多导致资源浪费,也能确保任务的有序执行。让我们一起开始,一步步地构建这个Golang协程池吧!

协程池

首先,让我们编写一个简单的多协程代码,并逐步进行优化,最终实现一个简化版的协程池。假如我们有10个任务需要处理。我们最简单做法就是直接使用go关键字直接去生成相应的协程即可,比如这样:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg  sync.WaitGroup
    wg.Add(5)
    for i := 1; i <= 5; i++ {
        go func(index int) {
            fmt.Printf("Goroutine %d started\n", index)
            time.Sleep(1 * time.Second)
            fmt.Printf("Goroutine %d finished\n", index)
            wg.Done()
        }(i)
    }
    wg.Wait()
    fmt.Println("All goroutines finished")
}

我们当前的用法非常简单,无需专门维护goroutine容量大小。每当有一个任务出现,我们就创建一个goroutine来处理。现在,让我们进一步优化这种用法。

优化版协程池

现在我们需要首先创建一个pool对象和一个专门用于运行协程的worker对象。如果你熟悉Java中线程池的源码,对于worker这个名称应该不会陌生。worker是一个专门用于线程复用的对象,而我们的worker同样负责运行任务。

实体Job没啥好说的,就是我们具体的任务。我们先来定义一个简单的任务实体Job:

type Job struct {
    id  int
    num int
}

我们来看下主角Worker定义:

type Worker struct {
    id         int
    jobChannel chan Job
    wg         *sync.WaitGroup
}

func NewWorker(id int, wga *sync.WaitGroup) *Worker {
    return &Worker{
        id:         id,
        jobChannel: make(chan Job, 1),
        wg:         wga,
    }
}

func (w *Worker) Start() {
    go func() {
        
        for job := range w.jobChannel {
            fmt.Printf("Worker %d is processing job %d\n", w.id, job.id)
            // 模拟任务处理
            fmt.Println("jobs 模拟任务处理")
            time.Sleep(2 * time.Second) // 休眠2秒钟
            result := job.num * 2
            fmt.Printf("Worker %d finished job %d, result: %d\n", w.id, job.id, result)
            w.wg.Done()
        }
    }()
}

你可以看到,我的实体拥有一个channel。这个channel类似于我们想要获取的任务队列。与Java中使用链表形式并通过独占锁获取共享链表这种临界资源的方式不同,我选择使用了Golang中的channel来进行通信。因为Golang更倾向于使用channel进行通信,而不是共享资源。所以,我选择了使用channel。为了避免在添加任务时直接阻塞,我特意创建了一个带有任务缓冲的channel。

在这里,Start()方法是一个真正的协程,我会从channel中持续获取任务,以保持这个协程不被销毁,直到没有任务可获取为止。这个逻辑类似于Java中的线程池。

让我们进一步深入地探讨一下Pool池的定义:

type Pool struct {
    workers []*Worker
    jobChan chan Job
    wg      sync.WaitGroup
}

func NewPool(numWorkers, jobQueueSize int) *Pool {
    pl := &Pool{
        workers: make([]*Worker, numWorkers),
        jobChan: make(chan Job, jobQueueSize),
    }
    for i := 0; i < numWorkers; i++ {
        pl.workers[i] = NewWorker(i+1, &pl.wg)
    }
    return pl
}

func (p *Pool) Start() {
    for _, worker := range p.workers {
        worker.Start()
    }
    go p.dispatchJobs()
}

func (p *Pool) dispatchJobs() {
    for job := range p.jobChan {
        worker := p.getLeastBusyWorker()
        worker.jobChannel <- job
    }
}

func (p *Pool) getLeastBusyWorker() *Worker {
    leastBusy := p.workers[0]
    for _, worker := range p.workers {
        if len(worker.jobChannel) < len(leastBusy.jobChannel) {
            leastBusy = worker
        }
    }
    return leastBusy
}

func (p *Pool) AddJob(job Job) {
    fmt.Println("jobs add")
    p.wg.Add(1)
    p.jobChan <- job
}

他的定义可能有点复杂,但是我可以用简单的语言向你解释,就是那些东西,只是用Golang的写法来实现,与Java的实现方式类似。

首先,我定义了一个名为worker的数组,用于存储当前存在的worker数量。这里并没有实现核心和非核心worker的区分。另外,我还创建了一个独立的channel,用于保存可缓冲任务的大小。这些参数在初始化时是必须提供的。

Start方法的主要目的是启动worker开始执行任务。首先,它会启动一个核心协程,等待任务被派发。然后,dispatchJobs方法会开始监听channel是否有任务,如果有任务,则会将任务分派给worker进行处理。在整个过程中,通过channel进行通信,没有使用链表或其他共享资源实体。需要注意的是,dispatchJobs方法在另一个协程中被调用,这是为了避免阻塞主线程。

getLeastBusyWorker方法是用来获取阻塞任务最少的worker的。这个方法的主要目标是保持任务平均分配。而AddJob方法则是用来直接向channel中添加job任务的,这个方法比较简单,不需要过多解释。

协程池最终实现

Java开发者的Golang进修指南:从0->1带你实现协程池

经过一系列的反复修改和优化,我们终于成功实现了一个功能完善且高效的Golang协程池。下面是最终版本的代码:

package main

import (
    "fmt"
    "sync"
    "time"
)

type Job struct {
    id  int
    num int
}

type Worker struct {
    id         int
    jobChannel chan Job
    wg         *sync.WaitGroup
}

func NewWorker(id int, wga *sync.WaitGroup) *Worker {
    return &Worker{
        id:         id,
        jobChannel: make(chan Job, 1),
        wg:         wga,
    }
}

func (w *Worker) Start() {
    go func() {
        
        for job := range w.jobChannel {
            fmt.Printf("Worker %d is processing job %d\n", w.id, job.id)
            // 模拟任务处理
            fmt.Println("jobs 模拟任务处理")
            time.Sleep(2 * time.Second) // 休眠2秒钟
            result := job.num * 2
            fmt.Printf("Worker %d finished job %d, result: %d\n", w.id, job.id, result)
            w.wg.Done()
        }
    }()
}

type Pool struct {
    workers []*Worker
    jobChan chan Job
    wg      sync.WaitGroup
}

func NewPool(numWorkers, jobQueueSize int) *Pool {
    pl := &Pool{
        workers: make([]*Worker, numWorkers),
        jobChan: make(chan Job, jobQueueSize),
    }
    for i := 0; i < numWorkers; i++ {
        pl.workers[i] = NewWorker(i+1, &pl.wg)
    }
    return pl
}

func (p *Pool) Start() {
    for _, worker := range p.workers {
        worker.Start()
    }
    go p.dispatchJobs()
}

func (p *Pool) dispatchJobs() {
    for job := range p.jobChan {
        worker := p.getLeastBusyWorker()
        worker.jobChannel <- job
    }
}

func (p *Pool) getLeastBusyWorker() *Worker {
    leastBusy := p.workers[0]
    for _, worker := range p.workers {
        if len(worker.jobChannel) < len(leastBusy.jobChannel) {
            leastBusy = worker
        }
    }
    return leastBusy
}

func (p *Pool) AddJob(job Job) {
    fmt.Println("jobs add")
    p.wg.Add(1)
    p.jobChan <- job
}

func main() {
    pool := NewPool(3, 10)
    pool.Start()
    // 添加任务到协程池
    for i := 1; i <= 15; i++ {
        pool.AddJob(Job{
            id:  i,
            num: i,
        })
    }

    // 等待所有任务完成
    pool.wg.Wait()
    close(pool.jobChan)
    fmt.Println("All jobs finished")
}

三方协程池

在这种场景下,既然已经有一个存在的场景,那么显然轮子是肯定有的。不论使用哪种编程语言,我们都可以探索一下,以下是Golang语言中关于三方协程池的实现工具。

ants: ants是一个高性能的协程池实现,支持动态调整协程池的大小,可以通过简单的API调用来将任务提交给协程池进行执行。官方地址:https://github.com/panjf2000/ants

gopool:gopool 是一个高性能的 goroutine 池,旨在重用 goroutine 并限制 goroutine 的数量。官方地址:https://github.com/bytedance/gopkg/tree/develop/util/gopool

这些库都提供了简单易用的API,可以方便地创建和管理协程池,同时也支持动态调整协程池的大小,以满足不同场景下的需求。

总结

当然,我写的简易版协程池还有很多可以优化的地方,比如可以实现动态扩容等功能。今天我们要简单总结一下协程池的优势,主要是为了降低资源开销。协程池的好处在于可以重复利用协程,避免频繁创建和销毁协程,从而减少系统开销,提高系统性能。此外,协程池还可以提高响应速度,因为一旦接收到任务,可以立即执行,不需要等待协程创建的时间。另外,协程池还具有增强可管理性的优点,可以对协程进行集中调度和统一管理,方便进行性能调优。文章来源地址https://www.toymoban.com/news/detail-814844.html

到了这里,关于Java开发者的Golang进修指南:从0->1带你实现协程池的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 🔥🔥Java开发者的Python快速进修指南:面向对象进阶

    在上一期中,我们对Python中的对象声明进行了初步介绍。这一期,我们将深入探讨对象继承、组合以及多态这三个核心概念。不过,这里不打算赘述太多理论,因为我们都知道,Python与Java在这些方面的主要区别主要体现在语法上。例如,Python支持多重继承,这意味着一个类可

    2024年02月05日
    浏览(28)
  • 🔥🔥Java开发者的Python快速进修指南:面向对象基础

    当我深入学习了面向对象编程之后,我首先感受到的是代码编写的自由度大幅提升。不同于Java中严格的结构和约束,Python在面向对象的实现中展现出更加灵活和自由的特性。它使用了一些独特的,如self和cls,这些不仅增强了代码的可读性,还提供了对类和实例的明确

    2024年02月05日
    浏览(33)
  • Java开发者的Python快速进修指南:掌握T检验

    T检验是一种用于比较两个独立样本均值差异的统计方法。它通过计算T值和P值来判断样本之间是否存在显著性差异。通常情况下,我们会有两组数据,例如一组实验组和一组对照组。 T检验的原假设是两组样本的均值相等,备假设是两组样本的均值不相等。T检验会计算一个

    2024年03月09日
    浏览(37)
  • Java开发者的Python快速进修指南:面向对象--高级篇

    首先,让我来介绍一下今天的主题。今天我们将讨论封装、反射以及单例模式。除此之外,我们不再深入其他内容。关于封装功能,Python与Java大致相同,但写法略有不同,因为Python没有修饰符。而对于反射来说,我认为它比Java简单得多,不需要频繁地获取方法和属性,而是

    2024年02月05日
    浏览(40)
  • 🔥🔥Java开发者的Python快速进修指南:面向对象--高级篇

    首先,让我来介绍一下今天的主题。今天我们将讨论封装、反射以及单例模式。除此之外,我们不再深入其他内容。关于封装功能,Python与Java大致相同,但写法略有不同,因为Python没有修饰符。而对于反射来说,我认为它比Java简单得多,不需要频繁地获取方法和属性,而是

    2024年02月05日
    浏览(39)
  • 🔥🔥Java开发者的Python快速进修指南:实战之简易跳表

    之前我已经将Python的基本语法与Java进行了比较,相信大家对Python也有了一定的了解。我不会选择去写一些无用的业务逻辑来加强对Python的理解。相反,我更喜欢通过编写一些数据结构和算法来加深自己对Python编程的理解。学习任何语言都一样。 通过编写数据结构和算法,不

    2024年02月05日
    浏览(34)
  • 🔥🔥Java开发者的Python快速进修指南:自定义模块及常用模块

    好的,按照我们平常的惯例,我先来讲一下今天这节课的内容,以及Java和Python在某些方面的相似之处。Python使用import语句来导入包,而Java也是如此。然而,两者之间的区别在于Python没有类路径的概念,它直接使用.py文件的文件名作为导入路径,并将其余的工作交给Python解释

    2024年02月05日
    浏览(40)
  • 🔥🔥Java开发者的Python快速进修指南:网络编程及并发编程

    今天我们将对网络编程和多线程技术进行讲解,这两者的原理大家都已经了解了,因此我们主要关注的是它们的写法区别。虽然这些区别并不是非常明显,但我们之所以将网络编程和多线程一起讲解,是因为在学习Java的socket知识时,我们通常会将它们结合使用,以实现服务器

    2024年02月05日
    浏览(29)
  • 🔥🔥Java开发者的Python快速进修指南:实战之跳表pro版本

    之前我们讲解了简易版的跳表,我希望你能亲自动手实现一个更完善的跳表,同时也可以尝试实现其他数据结构,例如动态数组或哈希表等。通过实践,我们能够发现自己在哪些方面还有所欠缺。这些方法只有在熟练掌握之后才会真正理解,就像我在编写代码的过程中,难免

    2024年02月05日
    浏览(28)
  • Java开发者的Python快速进修指南:实战之跳表pro版本

    之前我们讲解了简易版的跳表,我希望你能亲自动手实现一个更完善的跳表,同时也可以尝试实现其他数据结构,例如动态数组或哈希表等。通过实践,我们能够发现自己在哪些方面还有所欠缺。这些方法只有在熟练掌握之后才会真正理解,就像我在编写代码的过程中,难免

    2024年02月05日
    浏览(36)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包