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日

相关文章

  • Win11提示找不到文件请确定文件名是否正确怎么解决?

    Win11提示找不到文件的错误提示可能会出现在系统的各个部分,例如在桌面或文件资源管理器中打开文件夹,打开程序等操作时都有可能出现此类提示。此错误提示通常有以下几个原因: 文件被删除或移动,导致路径不正确,系统无法找到。 文件名中将中文空格、标点符号作为文件名,导致系统无法解析文件名。 文件被病毒或恶意软件感染,导致无法使用。 针对以上错误,我们可以尝试一下…

    other 2023年6月26日
    00
  • WinXP、Win7、Win8系统的电脑动态IP地址设置方法图文教程

    WinXP、Win7、Win8系统的电脑动态IP地址设置方法图文教程 1. 打开网络连接设置 首先,我们需要打开网络连接设置界面。在WinXP系统中,可以通过以下步骤打开: 单击\”开始\”按钮,选择\”控制面板\”。 在控制面板中,双击\”网络连接\”图标。 在Win7和Win8系统中,可以通过以下步骤打开: 单击\”开始\”按钮,选择\”控制面板\”。 …

    other 2023年7月30日
    00
  • Java获取本机IP地址的三种方法总结

    Java获取本机IP地址的三种方法总结 在Java中,有多种方法可以获取本机的IP地址。下面将介绍三种常用的方法,并提供示例说明。 方法一:使用InetAddress类 import java.net.InetAddress; import java.net.UnknownHostException; public class GetIPAddress { …

    other 2023年7月30日
    00
  • gunzip命令–解压文件

    gunzip命令 – 解压文件 gunzip命令是一个用于解压缩gzip文件的Linux命令。gzip是一种常见的压缩格式,它可以将文件压缩为更小的大小,以便更快地传输和存储。在本文中,我们将详细介绍如何使用gunzip命令解压缩gzip。 命令语法 gunzip命令的基本语法如下: gunzip [options] [filename] 其中,filena…

    other 2023年5月7日
    00
  • Java类加载的过程详解

    Java类加载的过程是指在Java应用程序运行时,JVM将类的.class文件加载到内存中,并对类进行解析,链接和初始化的过程。下面我们就来详细讲解一下Java类加载的过程。 Java类加载的过程 Java类加载的主要过程分为三个阶段:加载、链接和初始化。 加载 类加载是指在JVM内存中创建一个Class对象,用来表示加载的类。类加载的过程大致可以分为以下几…

    other 2023年6月25日
    00
  • DR5插件怎么安装?Delicious Retouch5.0汉化加强版安装教程+使用方法(win/mac)

    首先,说明一下DR5插件是什么? DR5是一款针对Adobe Photoshop的插件,也就是Photoshop插件。它可以帮助用户简化繁琐的后期修图工作,提高修图效率,增强修图效果。DR5插件功能众多,包括磨皮、美白、瘦脸、增强眼部、红润唇彩等,还可以针对不同肤色进行优化。 下面,我们来掌握Delicious Retouch5.0汉化加强版安装教程+使用方…

    other 2023年6月26日
    00
  • 易语言ocx控件制作条形码功能

    下面我会详细讲解“易语言OCX控件制作条形码功能”的完整攻略。 1. 准备工作 在开始制作条形码功能之前,我们需要做一些准备工作: 下载安装易语言开发环境。 下载安装条形码字体文件,如Code128字体、Code39字体等。 下载安装条形码生成库,并在易语言中添加该库文件。 2. 创建控件 在易语言中,我们可以通过如下代码来创建一个简单的OCX控件: ‘–…

    other 2023年6月26日
    00
  • 开发者福音:Google将Android默认字体Roboto完全开源了

    前言 在2011年,Google发布了一款新的字体“Roboto”,并将其作为Android操作系统的默认字体。随着Android的快速发展,Roboto字体已成为Android应用开发中最常用的字体之一。如今,Google宣布将Roboto字体完全开源,这对于开发者来说,是一份喜讯,下面我将为大家详细讲解使用Roboto字体的攻略。 下载Roboto字体文…

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