goroutine实践分享
创始人
2024-05-31 21:12:34
0

一.goroutine原理及GPM调度模型

  • 架构图

    GPM

  • 并发模型

​ 并发模型分为用户线程模型、内核线程模型、两级线程模型

​ 用户线程模型是用户线程和内核线程一对多映射模型。多个用户线程绑定到一个内核线程上,线程调度由用户线程库实现,系统对线程无感知,该模型线程调度由于不需要用户态到内核态切换,实现起来比较轻量,资源消耗少,但该模型中所有线程都绑定到同一个内核线程上,并没有实现真正的并发。

​ 内核线程模型是用户线程和内核线程一对一映射模型。内核线程模型实现相对简单,一个用户线程绑定到一个内核线程上,线程调度由系统实现,实现了真正意义上的并发,但在不同线程做切换需要用户态到内核态切换,资源消耗较大,对性能影响很大。

​ 两级线程模型是用户线程和内核线程n对m映射模型。该模型中n个用户线程和m个内核线程实现动态绑定,实现了系统级和用户级的两级调度模型,系统调度器调度内核线程,用户调度器调度用户级线程保证每个用户线 程公平调度,该模型实现了内核线程池的效果,可以大大减少内核线程创建、销毁的资源消耗和性能损失。

  • G-P-M模型

​ goroutine实现了两级线程模型,每一个go关键字都创建一个用户级线程,每一个被创建的线程都是一个可调度的执行单元。内核线程创建需要2mb的固定栈空间,而每一个go线程只需要2kb的栈空间,随着任务的执行,栈空间不足时,调度器会动态的给go线程分配另一块连续栈空间。内核线程的调度需要用户态到内核态的切换,非常耗费资源,而go线程调度由runtime在用户级别细线,资源消耗很少。

​ goroutine模型,抽象为三个结构G、P、M,G代表一个用户级别线程,G保存任务执行相关的堆栈和上线文状态。P代表一个逻辑处理器,每个P上都有一个G执行队列,P的数量决定了系统最大并行度,P的存在达到了内核线程池的效果。M代表一个系统线程,每个M在绑定一个P后开始执行上面的G,M不保存G的状态,这是G可以跨M调度的基础。

  • G-P-M调度

​ goroutine队列分为global任务队列和P维护的local任务队列。当通过go关键字创建一个新的goroutine的时候,它会优先被放入P的本地队列。为了运行goroutine,M需要持有(绑定)一个P,接着M会启动一个OS线程,循环从P的本地队列里取出一个goroutine并执行。当然还有上文提及的 work-stealing调度算法:当M执行完了当前P的Local队列里的所有G后,P也不会就这么在那躺尸啥都不干,它会先尝试从Global队列寻找G来执行,如果Global队列为空,它会随机挑选另外一个P,从它的队列里中拿走一半的G到自己的队列中执行。

  1. 用户态阻塞处理
    goroutine因为channle或sync同步阻塞时,该goroutine会被放置到某个wait队列,该G的状态由_Gruning变为_Gwaitting,而M会跳过该G尝试获取并执行下一个G,如果此时没有runnable的G供M运行,那么M将解绑P,并进入sleep状态;当阻塞的G被另一端的G2唤醒时,G被标记为runnable,尝试加入G2所在P的runnext,然后再是P的Local队列和Global队列。
  2. 内核态阻塞处理
    当G被阻塞在某个系统调用上时,此时G会阻塞在_Gsyscall状态,M也处于 block on syscall 状态,此时的M可被抢占调度:执行该G的M会与P解绑,而P则尝试与其它idle的M绑定,继续执行其它G。如果没有其它idle的M,但P的Local队列中仍然有G需要执行,则创建一个新的M;当系统调用完成后,G会重新尝试获取一个idle的P进入它的Local队列恢复执行,如果没有idle的P,G会被标记为runnable加入到Global队列。

二.应用实践

  • routinepool

    go协程虽然相比系统线程创建、销毁轻量仅需要2kb栈空间,协程间调度不需要用户态到内核态切换,但是如果大量频繁创建、销毁协程如10w、100w,则内存的消耗、协程调度cpu消耗仍需要浪费大量系统资源。这些消耗的资源大部分浪费在协程的创建、销毁上面,对此可以使用协程池技术,达到协程资源复用目的。

    routinepool协程库实现了固定协程池和弹性协程池两种协程池,简要介绍整体结构。

    协程池

type RoutinePool struct {......}func NewFixedRoutinePool(taskQueueSize int64, waitInterval time.Duration,normalWorkerSize int64) (rp *RoutinePool){ ...... }func NewCachedRoutinePool(taskQueueSize int64, waitInterval time.Duration,normalWorkerSize int64, maxWorkerSize int64) (rp *RoutinePool) {  ......  }func (rp *RoutinePool) Execute(task Runable) bool { ...... }func (rp *RoutinePool) ExecuteFunc(call func()) bool { ...... }func (rp *RoutinePool) Close() {  ......  }

​ 可执行任务接口

type Runable interface {Run()}

​ 工作协程

type Worker struct {......
}func NewWorker(tq chan Runable, ei time.Duration, rf bool, dc chan bool, cc chan struct{}) (w *Worker) {  ......  }func (w *Worker) Start() { ......  }

​ RoutinePool 对象实现了协程池管理功能,可以使用NewFixedRoutinePool创建固定协程池、NewCachedRoutinePool创建弹性协程池。执行的任务可以是实现Runable的接口或func()类型函数,通过Execute或ExecuteFunc函数放入协程池,等待worker队列执行。

  • gofuture

    go协程池使用虽然方便,但是创建后不容易控制,不能方便的终止其执行,也不能类似函数调用便捷的获取其执行结果。gofuture库对默认的go协程使用做了一层封装,可以控制协程的执行,获取协程执行结果。

    type Interface interface {Cancel()IsCancelled() boolGet() (interface{}, error)GetUntil(d time.Duration) (interface{}, bool, error)}func NewFuture(inFunc func() (interface{}, error)) Interface { ...... }
    

    NewFuture 产生一个内部实现了Interface接口的future对象。该对象提供了Get、GetUntil、Cancel、IsCancelled函数调用,Get函数获取inFunc执行结果;GetUntil函数同样是获取inFunc执行结果,但在inFunc未执行前会等待d时间段,超时则错误返回;Cancel函数取消inFunc任务执行;IsCancelled函数判断任务是否取消执行。

  • goscheduled

    在编程中经常会遇到某一类任务按照固定频率或者固定时间段多次执行,为此可以实现一个时间优先任务调度队列。

    队列管理对象

    type GoScheduled struct {......}func NewGoScheduled() (gs *GoScheduled) { ...... }func (gs *GoScheduled) ScheduledAtFixedRate(task routinepool.Runable, i time.Duration) bool { ......  }func (gs *GoScheduled) ScheduledAtFixedDelay(task routinepool.Runable, i time.Duration) bool { ...... }func (gs *GoScheduled) Close() { ...... }
    

    调度任务队列

type ScheduledQueue struct {......}func NewScheduledQueue() (sq *ScheduledQueue) { ...... }func (sq *ScheduledQueue) HeapPush(x interface{}) { ...... }func (sq *ScheduledQueue) HeapPop() (x interface{}) { ...... }

​ 调度任务

 	type Scheduled struct {......}func (s *Scheduled) Run() { ...... }

​ GoScheduled 实现了任务调度模块管理,可以通过ScheduledAtFixedRate函数调度固定频率任务的执行,ScheduledAtFixedDelay函数调度固定时间段间隔任务的执行。ScheduledQueue 使用优先队列实现任务按照时间优先级进行调度,HeapPush函数实现调度任务入队,HeapPop函数实现调度任务出队。Scheduled 实现一个具体可以被调度的任务。

相关内容

热门资讯

安卓系统对比骁龙,性能与生态的... 你有没有想过,为什么你的手机里装的是安卓系统,而不是苹果的iOS呢?又或者,为什么你的安卓手机里搭载...
qt程序安卓系统运行,基于Qt... 你有没有想过,为什么有些手机上的程序运行得那么顺畅,而有些却总是卡得让人抓狂?今天,就让我来给你揭秘...
安卓系统免费应用推荐,助你畅享... 手机里的应用是不是越来越多,有时候都挑花眼了呢?别急,今天我就来给你推荐一些安卓系统上的免费应用,让...
安卓系统视频通话app,打造无... 你有没有发现,现在手机上的视频通话功能越来越强大了?尤其是安卓系统上的那些视频通话app,简直让人爱...
安卓系统发现高危病毒,守护手机... 亲爱的手机用户们,最近可是有个大消息在安卓系统用户群里炸开了锅!没错,就是安卓系统发现了一款高危病毒...
安卓系统疯狂弹广告,揭秘广告软... 你有没有遇到过这种情况?手机里突然弹出一个广告,让你瞬间心情大崩溃?没错,说的就是安卓系统那让人头疼...
ebook 10进入安卓系统 你有没有发现,最近你的安卓手机里多了一个新伙伴——那就是电子书(ebook)10!没错,就是那个我们...
安卓系统如何调听筒,安卓系统调... 手机听筒声音突然变小了?别急,让我来教你如何轻松调教安卓系统的听筒,让它重新恢复活力!一、检查音量设...
安卓系统是怎么手机,解锁智能生... 你有没有想过,我们每天不离手的安卓手机,它背后的安卓系统究竟是怎么一回事呢?今天,就让我带你一探究竟...
安卓系统能代替windows系... 你有没有想过,我们日常使用的安卓系统和Windows系统,哪个才是真正的霸主呢?是不是有时候觉得安卓...
lp108安卓系统,功能特点与... 你有没有听说最近LP108安卓系统火得一塌糊涂?没错,就是那个让无数手机用户都为之疯狂的新系统!今天...
安卓系统挂载u盘,轻松实现数据... 你有没有想过,你的安卓手机或平板电脑突然变成了一个移动的U盘?没错,就是那种可以随意存取文件的神奇设...
i5 安卓系统,引领智能终端新... 你有没有想过,为什么你的手机总是卡得要命,而别人的手机却能流畅如丝?是不是因为你的手机搭载了那个传说...
安卓手机系统没有升级,揭秘潜在... 你有没有发现,你的安卓手机系统好像好久没升级了呢?是不是觉得有点out了?别急,今天就来给你详细聊聊...
安卓14系统定制v,创新功能与... 你知道吗?最近安卓系统又出新花样了!安卓14系统定制版V,这名字听起来就让人兴奋不已。今天,就让我带...
手机安卓系统越高越好,探索最新... 你有没有发现,每次手机更新系统,你的手机就像脱胎换骨了一样?没错,说的就是你,那个安卓手机!今天,咱...
鸿蒙系统怎么用回安卓,轻松实现... 你是不是也和我一样,对鸿蒙系统的新鲜感还没过,却又忍不住想回到熟悉的安卓世界?别急,今天就来手把手教...
苹果7跟安卓系统,性能对决与用... 你有没有想过,为什么苹果7那么受欢迎,而安卓系统却有着庞大的用户群体?今天,我们就来聊聊这个话题,看...
安卓手机刷简化系统,轻松实现流... 你有没有想过,你的安卓手机其实可以变得更加轻快、流畅呢?没错,就是通过刷简化系统!今天,就让我带你一...
社保掌上通安卓系统,轻松掌握在... 你有没有发现,现在的生活越来越离不开手机了?无论是购物、聊天还是办公,手机都能轻松搞定。这不,今天就...