Golang协程池gopool设计与实现

Golang协程池gopool设计与实现

协程池的概念

在 Golang 中,我们可以通过 Go 关键字,轻松创建协程(也称作 goroutine),但这种方式也会导致大量的协程被创建,如果这些协程的生命周期很短,那么会导致频繁的创建和销毁,带来较大的系统开销。此时,协程池就应运而生了。协程池的工作原理是,创建一些协程并将它们放到一个池子里面,并在需要使用协程时,从池子中取出一个协程来执行任务,任务完成后再将协程放回池子中,从而避免了频繁地创建和销毁协程。

gopool的实现原理

gopool 是一个 Golang 协程池的工具库,通过封装 sync.Pool 和 Golang 协程实现。gopool 提供了一个分配协程的入口,我们可以在程序里调用该函数来申请一个协程来执行任务。

gopool 包含以下几个部分的实现:

协程池结构体

type Pool interface {
    Stop()
    Serve(context.Context) error
}

type pool struct {
    mu          sync.Mutex
    cond        *sync.Cond
    cap         int             // 协程池容量
    running     int             // 正在执行的协程数
    workers     []*worker       // 协程池所包含的工人
    expiredTime time.Duration   // 协程空闲超时时间
    ctx         context.Context // 上下文
    cancel      context.CancelFunc
}

工人结构体

type worker struct {
    pool *pool
    task chan func() // 实际执行任务的通道
    used int64       // 工人使用次数
    expire int64     // 工人的过期时间
}

协程池运行方法

func (p *pool) Serve(ctx context.Context) error {
    for {
        select {
        case <-ctx.Done():
            return nil
        default:
        }

        if len(p.workers) >= p.cap {
            return nil // 达到最大并发数量
        }

        w := &worker{
            pool: p,
            task: make(chan func(), 1),
        }
        p.mu.Lock()
        p.workers = append(p.workers, w)
        p.mu.Unlock()

        go w.run()

        select {
        case <-ctx.Done():
            return nil
        default:
        }
    }
}

工人运行方法

func (w *worker) run() {
    for {
        w.pool.cond.L.Lock() // 获取锁

        for len(w.task) == 0 {
            when := time.Now().UnixNano()

            // 判断是否已经过期了
            d := w.expire - when
            if d <= 0 {
                w.pool.removeWorker(w)
                w.pool.cond.L.Unlock()
                return // 过期了,退出
            }

            w.pool.cond.L.Unlock()

            time.Sleep(time.Duration(d))

            w.pool.cond.L.Lock()
        }

        // 从任务队列中收取任务
        task := <-w.task
        w.pool.running++
        w.pool.cond.L.Unlock()

        // 执行任务
        task()

        // 通知任务已完成,并放回任务池中
        w.pool.cond.L.Lock()
        w.pool.running--
        w.pool.putTask(task)
        w.pool.cond.L.Unlock()
    }
}

协程池停止方法

为了保证在没有任务时,协程池可以正确的退出,需要实现一个协程锁,用来控制工作协程停止工作。

// 停止协程池
func (p *pool) Stop() {
    p.cancel()
    p.mu.Lock()

    for _, worker := range p.workers {
        worker.stop()
    }

    p.workers = nil
    p.running = 0

    p.mu.Unlock()
}

gopool使用示例

下面,我们就来看两个使用 gopool 的例子:计算斐波那契数列和从文件中读入数据。

计算斐波那契数列

func Fibonacci(n int) int {
    if n == 0 {
        return 0
    }

    if n == 1 {
        return 1
    }

    return Fibonacci(n-1) + Fibonacci(n-2)
}

// 计算斐波那契数列和
func CalculateFibonacci(n int) int {
    if n <= 0 {
        return 0
    }

    if n == 1 {
        return 1
    }

    task := make(chan func())

    p := newGoroutinePool(10, 10)
    defer p.Stop()

    for i := 0; i < n; i++ {
        i := i
        p.Execute(func() {
            task <- func() {
                Fibonacci(i)
            }
        }, nil)
    }

    result := make(chan int, n)

    for i := 0; i < n; i++ {
        p.Execute(nil, func() {
            result <- <-task
        })
    }

    total := 0
    for i := 0; i < n; i++ {
        total += <-result
    }

    return total
}

从文件中读入数据

func countWords(fn string, filter func(string) bool) (int, error) {
    var count int

    file, err := os.Open(fn)
    if err != nil {
        return 0, err
    }

    defer file.Close()

    scanner := bufio.NewScanner(file)

    task := make(chan func())

    p := newGoroutinePool(10, 10)
    defer p.Stop()

    for scanner.Scan() {
        line := scanner.Text()

        p.Execute(func() {
            task <- func() {
                if filter == nil || filter(line) {
                    count += len(strings.Split(line, " "))
                }
            }
        }, nil)
    }

    if err := scanner.Err(); err != nil {
        return 0, err
    }

    for {
        if len(task) == 0 {
            break
        }

        p.Execute(nil, func() {
            <-task
        })
    }

    return count, nil
}

以上是 gopool 的使用示例,可以看出,gopool 的使用相对简单,通过传入一个需要执行的函数即可。同时,也可以从以上示例中发现 gopool 的协程池确实可以很好的提高程序的性能。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Golang协程池gopool设计与实现 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • Java中String对象的深入理解

    Java中String对象的深入理解 String是Java中最常用的类之一,用于表示字符串。在Java中,String对象是不可变的,这意味着一旦创建了String对象,就不能修改其内容。在本攻略中,我们将深入理解Java中String对象的特性和用法。 1. String的创建和初始化 在Java中,有多种方式可以创建和初始化String对象: 直接赋值…

    other 2023年10月15日
    00
  • ReactJS入门实例教程详解

    ReactJS入门实例教程详解 ReactJS是Facebook开发的一款基于组件化的前端框架,它能够有效地提升前端的开发效率并且具有很好的可维护性。本教程将详细介绍ReactJS的基本概念和使用方法,包括组件的定义、状态的管理、事件的处理等内容,通过实例来演示ReactJS的强大功能。 ReactJS基本概念 ReactJS的核心概念是组件(Compone…

    other 2023年6月27日
    00
  • win8系统开机提示“要使用本计算机,用户必须输入用户名和密码”的解决方法

    下面是详细讲解“win8系统开机提示“要使用本计算机,用户必须输入用户名和密码”的解决方法”的完整攻略。 问题描述 在使用Win8系统时,有可能会遇到开机提示“要使用本计算机,用户必须输入用户名和密码”的情况。这一提示会要求用户输入用户名和密码才能够进入系统,但是对于一些用户来说,这些操作显得有些繁琐和麻烦。 解决方法 要解决这个问题,有两种方法可以尝试。 …

    other 2023年6月27日
    00
  • python下setuptools的安装详解及No module named setuptools的解决方法

    Python下setuptools的安装详解及No module named setuptools的解决方法 前言 在Python开发过程中,经常需要使用第三方库。对于Python的库管理和安装,使用pip命令可以非常方便地完成。但是,在有些情况下,直接使用pip安装某个库时,会提示“no module named ‘xxx’”的错误。这时,可能就需要安装s…

    other 2023年6月27日
    00
  • Snagit for mac(截图软件)中文版,截个图就是这么容易!

    下面是关于Snagit for Mac截图软件的完整攻略,包括软件介绍、使用方法和两个示例等方面。 软件介绍 Snagit for Mac是一款功能强大的截图软件,它可以帮助用户快速、方便地进行屏幕截图、视频录制、图像编辑等操作。该软件具有简单易用、功能丰富、界面友好等特点,是Mac用户进行截图和录屏的首选工具。 使用方法 使用Snagit for Mac进…

    other 2023年5月6日
    00
  • PPT怎么制作毕业纪念册封面动画?

    下面是“PPT怎么制作毕业纪念册封面动画”的完整攻略。 一、思路阶段 确定封面主题和色彩。在开始制作封面动画之前,首先需要考虑的就是封面的主题和色彩。毕业纪念册封面通常要体现毕业的主题,例如“飞翔”、“成长”等等。同时,颜色的搭配也很重要,可以考虑采用学校的代表色或者与毕业主题相关的颜色。 制定动画需求。在确定了封面的主题和色彩之后,需要考虑动画的需求。例如…

    other 2023年6月27日
    00
  • iPhone XS/XS Max/XR怎么强制重启?苹果手机强制重启教程

    下面是详细的攻略: 如何强制重启iPhone XS/XS Max/XR? 在某些情况下,您的 iPhone XS / XS Max / XR 可能无法响应操作或运行缓慢。当您遇到这种情况时,您可以尝试强制重启您的设备,这通常可以解决问题。 以下是 iPhone XS / XS Max / XR 强制重启的步骤: 按住侧边的“音量加”按钮和侧边的“电源”按钮,…

    other 2023年6月27日
    00
  • vs2017子类怎么访问父类同名静态成员?

    当子类与父类拥有同名的静态成员时,可以通过使用 ” 父类名:: ” 来访问父类中的静态成员。 例如,以下是一个父类及其子类的示例代码: #include <iostream> class Parent { public: static int x; }; int Parent::x = 10; class Child : public Paren…

    other 2023年6月26日
    00
合作推广
合作推广
分享本页
返回顶部