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 实现一个具体可以被调度的任务。

相关内容

热门资讯

安卓手机哪个系统没广告,揭秘无... 你有没有发现,用安卓手机的时候,广告总是无处不在,让人有点头疼呢?今天,就让我来给你揭秘安卓手机中哪...
安卓没电系统提示音,唤醒你的紧... 手机没电了,是不是瞬间感觉整个人都不好了?尤其是安卓手机,那系统提示音一响,简直就像是在耳边嗡嗡嗡地...
安卓系统笔记本系统,探索安卓系... 你有没有想过,为什么你的手机和电脑有时候会闹别扭呢?其实,这背后的大佬就是安卓系统和笔记本系统。今天...
安卓电视系统升级失败,原因排查... 最近是不是你也遇到了安卓电视系统升级失败的问题?这可真是让人头疼啊!想象你正准备享受一部精彩的电影,...
安卓系统怎么运用无线充,安卓系... 你有没有想过,手机充电也能变得这么酷炫?没错,就是无线充电!现在,越来越多的安卓手机都支持无线充电功...
系统翻译软件好用吗安卓,好用度... 你有没有想过,当你拿着手机,想要翻译一段外文资料时,是不是会突然觉得世界变得如此宽广?没错,说的就是...
麒麟系统安卓手机传文件,轻松实... 你有没有想过,手机传文件竟然也能变得如此轻松愉快?没错,就是那个麒麟系统安卓手机,它可是让我们的文件...
安卓系统进命令模式,Andro... 你有没有想过,你的安卓手机其实是个小机器人?没错,它不仅能听你说话,还能按照你的指令去执行任务呢!今...
安卓无触摸系统软件,创新交互体... 你有没有想过,即使没有触摸屏,你的安卓手机也能玩得风生水起?没错,就是安卓无触摸系统软件!今天,就让...
安卓系统和ios系统哪个更流畅... 你有没有想过,为什么你的手机有时候像蜗牛一样慢吞吞的,而别人的手机却能像闪电一样快?这背后,其实就是...
x98进安卓系统,性能升级与全... 你知道吗?最近手机圈里可是炸开了锅,因为一款名为x98的设备要升级到安卓系统了!这可不仅仅是简单的系...
安卓系统官网版本下载,从官网获... 你有没有发现,每次手机更新系统,都像是在给我们的生活添上一抹新的色彩呢?没错,今天咱们就来聊聊这个让...
安卓11系统玩mugen教程,... 你有没有想过,用安卓11系统玩Mugen游戏,那感觉简直就像是在手机上开了一场虚拟的格斗大赛!没错,...
安卓系统如何全页截图,安卓全页... 亲爱的手机控们,你是否有过这样的瞬间:看到手机屏幕上某个精彩瞬间,想要截图保存,却发现截图只截到了一...
安卓系统曝光作用原理,揭秘安卓... 你知道吗?最近安卓系统又曝光了一些新动向,这让我这个科技爱好者兴奋不已。今天,就让我带你一探究竟,揭...
安卓盲区监测系统有哪些,安全驾... 你有没有想过,开车的时候,突然一个盲区里的“小家伙”冒了出来,那感觉简直就像是从天而降的惊吓!别担心...
安卓还是苹果系统好用吗,谁更胜... 说到手机系统,安卓和苹果的较量可是从手机诞生之初就开始了。你有没有想过,到底哪个系统更适合你呢?今天...
设置安卓系统的默认相机,功能与... 你有没有发现,每次打开手机拍照,相机界面总是那个老样子?是不是有点审美疲劳了呢?别急,今天就来教你怎...
抖音支持的安卓系统,支持版本全... 你有没有发现,最近抖音可是越来越火了呢?不管是在地铁里、公交车上,还是在家里,总能看到大家刷着抖音,...
安卓系统怎么把文件导出,例如使... 你是不是也和我一样,手机里存了好多宝贝文件,想分享给朋友或者备份到电脑上呢?别急,今天就来教你怎么把...