iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Golang协程池gopool怎么设计与实现
  • 715
分享到

Golang协程池gopool怎么设计与实现

2023-06-30 03:06:42 715人浏览 薄情痞子
摘要

这篇文章主要介绍“golang协程池Gopool怎么设计与实现”,在日常操作中,相信很多人在Golang协程池gopool怎么设计与实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang协程池gopo

这篇文章主要介绍“golang协程池Gopool怎么设计与实现”,在日常操作中,相信很多人在Golang协程池gopool怎么设计与实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang协程池gopool怎么设计与实现”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

Goroutine

Goroutine 是 Golang 提供的一种轻量级线程,我们通常称之为「协程」,相比较线程,创建一个协程的成本是很低的。所以你会经常看到 Golang 开发的应用出现上千个协程并发的场景。

Goroutine 的优势:

  • 与线程相比,Goroutines 成本很低。

它们的堆栈大小只有几 kb,堆栈可以根据应用程序的需要增长和缩小,context switch 也很快,而在线程的情况下,堆栈大小必须指定并固定。

  • Goroutine 被多路复用到更少数量的 OS 线程。

一个包含数千个 Goroutine 的程序中可能只有一个线程。如果该线程中的任何 Goroutine 阻塞等待用户输入,则创建另一个 OS 线程并将剩余的 Goroutine 移动到新的 OS 线程。所有这些都由运行时处理,作为开发者无需耗费心力关心,这也使得我们有很干净的 api 来支持并发。

  • Goroutines 使用 channel 进行通信。

channel 的设计有效防止了在使用 Goroutine 访问共享内存时发生竞争条件(race conditions) 。channel 可以被认为是 Goroutine 进行通信的管道。

协程池

高并发场景下,我们可能会启动大量的协程来处理业务逻辑。协程池是一种利用池化技术,复用对象,减少内存分配的频率以及协程创建开销,从而提高协程执行效率的技术。

最近抽空了解了字节官方开源的 gopkg 库提供的 gopool 协程池实现,感觉还是很高质量的,代码也非常简洁清晰,而且 Kitex 底层也在使用 gopool 来管理协程,这里我们梳理一下设计和实现。

gopool

了解官方 README 就会发现gopool的用法其实非常简单,将曾经我们经常使用的 go func(){...} 替换为 gopool.Go(func(){...}) 即可。

此时 gopool 将会使用默认的配置来管理你启动的协程,你也可以选择针对业务场景配置池子大小,以及扩容上限。

old:

go func() {// do your job}()

new:

import (    "GitHub.com/bytedance/gopkg/util/gopool")gopool.Go(func(){/// do your job})

核心实现

下面我们来看看gopool是怎样实现协程池管理的。

Pool

Pool 是一个定义了协程池能力的接口。

type Pool interface {// 池子的名称Name() string        // 设置池子内Goroutine的容量SetCap(cap int32)        // 执行 f 函数Go(f func())        // 带 ctx,执行 f 函数CtxGo(ctx context.Context, f func())        // 设置发生panic时调用的函数SetPanicHandler(f func(context.Context, interface{}))}

gopool 提供了这个接口的默认实现(即下面即将介绍的pool),当我们直接调用 gopool.CtxGo 时依赖的就是这个。

这样的设计模式Kitex 中也经常出现,所有的依赖均设计为接口,便于随后扩展,底层提供一个默认的实现暴露出去,这样对调用方也很友好。

type pool struct {// 池子名称name string// 池子的容量, 即最大并发工作的 goroutine 的数量cap int32        // 池子配置config *Config        // task 链表taskHead  *tasktaskTail  *tasktaskLock  sync.MutextaskCount int32// 记录当前正在运行的 worker 的数量workerCount int32// 当 worker 出现panic时被调用panicHandler func(context.Context, interface{})}// NewPool 创建一个新的协程池,初始化名称,容量,配置func NewPool(name string, cap int32, config *Config) Pool {p := &pool{name:   name,cap:    cap,config: config,}return p}

调用 NewPool 获取了以 Pool 的形式返回的 pool 结构体。

Task

type task struct {ctx context.Contextf   func()next *task}

task 是一个链表结构,可以把它理解为一个待执行的任务,它包含了当前节点需要执行的函数f, 以及指向下一个task的指针。

综合前一节 pool 的定义,我们可以看到,一个协程池 pool 对应了一组task

pool 维护了指向链表的头尾的两个指针:taskHeadtaskTail,以及链表的长度taskCount 和对应的 taskLock

Worker

type worker struct {pool *pool}

一个 worker 就是逻辑上的一个执行器,它唯一对应到一个协程池 pool。当一个worker被唤起,将会开启一个goroutine ,不断地从 pool 中的 task链表获取任务并执行。

func (w *worker) run() {go func() {for {                        // 声明即将执行的 taskvar t *task                                                // 操作 pool 中的 task 链表,加锁w.pool.taskLock.Lock()if w.pool.taskHead != nil {                                // 拿到 taskHead 准备执行t = w.pool.taskHead                                                                // 更新链表的 head 以及数量w.pool.taskHead = w.pool.taskHead.nextatomic.AddInt32(&w.pool.taskCount, -1)}                        // 如果前一步拿到的 taskHead 为空,说明无任务需要执行,清理后返回if t == nil {w.close()w.pool.taskLock.Unlock()w.Recycle()return}w.pool.taskLock.Unlock()                                                // 执行任务,针对 panic 会recover,并调用配置的 handlerfunc() {defer func() {if r := recover(); r != nil {msg := fmt.Sprintf("GOPOOL: panic in pool: %s: %v: %s", w.pool.name, r, debug.Stack())logger.CtxErrorf(t.ctx, msg)if w.pool.panicHandler != nil {w.pool.panicHandler(t.ctx, r)}}}()t.f()}()t.Recycle()}}()}

整体来看

看到这里,其实就能把整个流程串起来了。我们来看看对外的接口 CtxGo(context.Context, f func()) 到底做了什么?

func Go(f func()) {CtxGo(context.Background(), f)}func CtxGo(ctx context.Context, f func()) {defaultPool.CtxGo(ctx, f)}func (p *pool) CtxGo(ctx context.Context, f func()) {        // 创建一个 task 对象,将 ctx 和待执行的函数赋值t := taskPool.Get().(*task)t.ctx = ctxt.f = f                // 将 task 插入 pool 的链表的尾部,更新链表数量p.taskLock.Lock()if p.taskHead == nil {p.taskHead = tp.taskTail = t} else {p.taskTail.next = tp.taskTail = t}p.taskLock.Unlock()atomic.AddInt32(&p.taskCount, 1)                // 以下两个条件满足时,创建新的 worker 并唤起执行:// 1. task的数量超过了配置的限制 // 2. 当前运行的worker数量小于上限(或无worker运行)if (atomic.LoadInt32(&p.taskCount) >= p.config.ScaleThreshold && p.WorkerCount() < atomic.LoadInt32(&p.cap)) || p.WorkerCount() == 0 {                        // worker数量+1p.incWorkerCount()                                // 创建一个新的worker,并把当前 pool 赋值w := workerPool.Get().(*worker)w.pool = p                                // 唤起worker执行w.run()}}

相信看了代码注释,大家就能理解发生了什么。

gopool 会自行维护一个 defaultPool,这是一个默认的 pool 结构体,在引入包的时候就进行初始化。当我们直接调用 gopool.CtxGo() 时,本质上是调用了 defaultPool 的同名方法

func init() {defaultPool = NewPool("gopool.DefaultPool", 10000, NewConfig())}const (defaultScalaThreshold = 1)// Config is used to config pool.type Config struct {// 控制扩容的门槛,一旦待执行的 task 超过此值,且 worker 数量未达到上限,就开始启动新的 workerScaleThreshold int32}// NewConfig creates a default Config.func NewConfig() *Config {c := &Config{ScaleThreshold: defaultScalaThreshold,}return c}

defaultPool 的名称为 gopool.DefaultPool,池子容量一万,扩容下限为 1。

当我们调用 CtxGo时,gopool 就会更新维护的任务链表,并且判断是否需要扩容 worker

  • 若此时已经有很多 worker 启动(底层一个 worker 对应一个 goroutine),不需要扩容,就直接返回。

  • 若判断需要扩容,就创建一个新的worker,并调用 worker.run()方法启动,各个worker会异步地检查 pool 里面的任务链表是否还有待执行的任务,如果有就执行。

三个角色的定位

  • task 是一个待执行的任务节点,同时还包含了指向下一个任务的指针,链表结构;

  • worker 是一个实际执行任务的执行器,它会异步启动一个 goroutine 执行协程池里面未执行的task

  • pool 是一个逻辑上的协程池,对应了一个task链表,同时负责维护task状态的更新,以及在需要的时候创建新的 worker

使用 sync.Pool 进行性能优化

其实到这个地方,gopool已经是一个代码简洁清晰的协程池库了,但是性能上显然有改进空间,所以gopool的作者应用了多次 sync.Pool 来池化对象的创建,复用woker和task对象。

这里建议大家直接看源码,其实在上面的代码中已经有所涉及。

  • task 池化

var taskPool sync.Poolfunc init() {taskPool.New = newTask}func newTask() interface{} {return &task{}}func (t *task) Recycle() {t.zero()taskPool.Put(t)}
  • worker 池化

var workerPool sync.Poolfunc init() {workerPool.New = newWorker}func newWorker() interface{} {return &worker{}}func (w *worker) Recycle() {w.zero()workerPool.Put(w)}

到此,关于“Golang协程池gopool怎么设计与实现”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

您可能感兴趣的文档:

--结束END--

本文标题: Golang协程池gopool怎么设计与实现

本文链接: https://www.lsjlt.com/news/327265.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

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

下载Word文档
猜你喜欢
  • Golang协程池gopool设计与实现
    目录Goroutine协程池gopool核心实现PoolTaskWorker整体来看三个角色的定位使用 sync.Pool 进行性能优化Goroutine Goroutine 是 G...
    99+
    2024-04-02
  • Golang协程池gopool怎么设计与实现
    这篇文章主要介绍“Golang协程池gopool怎么设计与实现”,在日常操作中,相信很多人在Golang协程池gopool怎么设计与实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang协程池gopo...
    99+
    2023-06-30
  • Golang协程池的实现与应用
    目录1. 为什么需要协程池2. 使用协程池的优点3. 设计思路4. 实现一个简单的协程池4.1 Task 任务对象4.2 Pool协程池4.3 Main函数1. 为什么需要协程池 使...
    99+
    2023-05-19
    Golang协程池 Go协程池使用
  • golang怎么实现线程池
    在Golang中,可以使用goroutine和channel来实现线程池。下面是一个简单的示例: package main imp...
    99+
    2023-10-21
    golang
  • golang线程池怎么实现
    在Go语言中,可以使用`goroutine`和`channel`来实现线程池。下面是一个简单的线程池实现示例: package ma...
    99+
    2023-10-27
    golang
  • java基于quasar怎么实现协程池
    这篇文章主要讲解了“java基于quasar怎么实现协程池”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java基于quasar怎么实现协程池”吧!业务场景:golang与swoole都拥抱...
    99+
    2023-07-02
  • golang协程池模拟实现群发邮件功能
    比如批量群发邮件的功能 因为发送邮件是个比较耗时的操作, 如果是传统的一个个执行 , 总体耗时比较长 可以使用golang实现一个协程池 , 并行发送邮件 pool包下的pool.g...
    99+
    2024-04-02
  • GO语言怎么实现协程池管理
    本篇内容介绍了“GO语言怎么实现协程池管理”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!使用channel实现协程池通过 Channel 实...
    99+
    2023-06-20
  • PHP底层的线程池与协程实现方法
    PHP底层的线程池与协程实现方法在PHP编程中,线程池和协程是提高性能和并发能力的重要方法。本文将介绍PHP底层实现线程池和协程的方法,并提供具体代码示例。一、线程池的实现线程池是一种重用线程的机制,可以提高多线程应用程序的性能。在PHP中...
    99+
    2023-11-08
    线程池 PHP底层 协程实现方法
  • 浅谈Go连接池的设计与实现
    目录为什么需要连接池连接池设计GetPut总结开源实现Get:Put:sql.DB为什么需要连接池 如果不用连接池,而是每次请求都创建一个连接是比较昂贵的,因此需要完成3次tcp握手...
    99+
    2023-05-15
    Go连接池
  • Go连接池设计与实现的方法是什么
    这篇“Go连接池设计与实现的方法是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go连接池设计与实现的方法是什么”文章吧...
    99+
    2023-07-06
  • 怎么设计与实现Redis
    本篇内容主要讲解“怎么设计与实现Redis”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么设计与实现Redis”吧!Redis的设计与实现其实 Redis 主要是通过三个方面来满足这样高效吞吐...
    99+
    2023-06-16
  • golang协程实现原理是什么
    Golang协程实现的原理是使用了一种称为"轻量级线程"或"用户态线程"的概念,即Goroutine(协程)。在Goroutine中...
    99+
    2023-08-31
    golang
  • Golang RabbitMQ: 实现可靠消息传递的设计与实现
    在Golang中,可以使用RabbitMQ来实现可靠消息传递。RabbitMQ是一个开源的消息中间件,它实现了AMQP(Advanc...
    99+
    2023-10-20
    Golang
  • Golang中HTTP路由设计的使用与实现
    目录Golang之HTTP路由设计动手编写自己的路由framework/core.goframework/group.go如何实现动态路由改造一下core.go验证Golang之HT...
    99+
    2023-05-19
    Golang HTTP路由设计 Golang路由实现
  • golang协程实现的原理是什么
    Golang中的协程(goroutine)是一种轻量级的线程,由Go语言的运行时系统进行管理。协程的实现原理主要包括以下几个方面: ...
    99+
    2023-10-25
    golang
  • Golang中怎么实现一个工作池
    本篇文章为大家展示了Golang中怎么实现一个工作池,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。worker pool简介worker pool其实就是线程池th...
    99+
    2024-04-02
  • Python协程怎么实现
    这篇文章主要讲解了“Python协程怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python协程怎么实现”吧!1.协程协程不是计算机提供的,计算机只提供:进程、线程。协程时人工创造...
    99+
    2023-07-05
  • Golang的中间件设计模式怎么实现
    这篇文章主要讲解了“Golang的中间件设计模式怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang的中间件设计模式怎么实现”吧!Demo所以接下来我们就来看看demo吧 首先...
    99+
    2023-07-05
  • 设计与实现Golang中链表的数据结构
    Golang中链表数据结构的设计与实现 引言:链表是一种常见的数据结构,用于存储一系列的节点。每个节点包含数据和指向下一个节点的指针。在Golang中,我们可以通过使用结构体和指针来实现链表。 链表的设计与结...
    99+
    2024-01-29
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作