goroutine调度模型 调度策略

文章目录

    • 背景
  • 协程
    • 线程与协程的对比
      • 线程(Thread)
      • 协程(Coroutine)
    • 运作
    • 线程模型
  • goroutine调度模型与演进过程
    • G-M模型
    • G-P-M模型
    • 抢占式调度器
    • 其他优化
  • 调度策略
    • 队列轮转
    • 系统调用
    • 工作量窃取
    • 抢占式调度
    • GOMAXPROCS 对性能的影响

Go在语言层面直接提供对协程的支持称为goroutine

背景

多核处理器硬件成为数据中心的主流

传统单线程的应用是难以发挥出多核硬件的威力的。相比于传统硬件的单个高主频处理器,传统单线程应用运行在多核硬件上之后,性能反而下降很多,这是因为它仅仅能使用到一颗物理多核处理器中的一个核(计算引擎)。

要想充分利用多核的强大计算能力,一般有两种方案,并行和并发,为了帮助更好的理解这两个的区别,下面给出一个地铁安检的例子:

假设凤起路地铁站只有一个安检通道,里面一共三个人,分别负责:随身物品安检、身体安检、液体安检。乘客只有完整走过一个流程之后,才能有下一个乘客,当客流量上来时,他们迫不得已的三个人同时工作,这就是并发;然后这个时候身后的队伍已经排到了龙翔桥,只能打开闲置的安检通道,这就是并行。

接下来用专业术语解释:

并发方案:并发就是重新做应用结构设计,即将应用分解成多个在基本执行单元(图中这样的执行单元为操作系统线程)中执行的、可能有一定关联关系的代码片段(图中的模块1~模块N)。我们看到与并行方案中应用自身结构无须调整有所不同,并发方案中应用自身结构做出了较大调整,应用内部拆分为多个可独立运行的模块。这样虽然应用仍然以单实例的方式运行,但其中的每个内部模块都运行于一个单独的操作系统线程中,多核资源得以充分利用。基于多线程模型的应用设计就是一种典型的并发程序设计。

并行方案:在处理器核数充足的情况下启动多个单线程应用的实例,这样每个实例“运行”在一个核上(如图中的CPU核1~CPU核N),尽可能多地利用多核计算资源。理论上在这种方案下应用的整体处理能力是与实例数量(小于或等于处理器核数)成正比的。但这种方案是有约束的,对于那些不支持在同一环境下部署多实例或同一用户仅能部署一个实例的应用,用传统的部署方式使之并行运行是有难度的甚至是无法实现的。不过近些年兴起的轻量级容器技术(如Docker)可以在一定程度上促成此方案

并发不是并行,并发关乎结构,并行关乎执行。

协程

为了方便理解,这里解释一下进程、线程、协程

进程:应用程序的启动实例,每个进程有独立的内存空间,不同进程通过进程间的通信方式来通信

线程:属于进程,每个进程至少一个线程,线程是CPU调度的基本单位,可以共享进程的资源,多线程之间通过共享内存等线程间的通信方式进行交流

协程:可以理解为轻量级线程,但不受操作系统调度,由协程调度器负责,协程调度器按照调度策略把协程调度到线程中运行

线程与协程的对比

线程(Thread)和协程(Coroutine)都是用于并发执行的编程概念,它们有各自的优势和劣势,适用于不同的场景。以下是线程和协程的一些比较:

线程(Thread)

优势:

  1. 多核利用: 线程通常由操作系统的调度器进行管理,可以在多个核上并行执行。这使得线程适用于需要充分利用多核 CPU 的计算密集型任务。
  2. 底层硬件支持: 线程通常由操作系统内核直接支持,因此它们可以直接与底层硬件进行交互,执行底层系统调用。
  3. 广泛的支持: 多数编程语言和操作系统都提供了线程支持,这使得线程在编写跨平台的并发代码时更为方便。

劣势:

  1. 资源消耗: 每个线程都需要一定的内存和操作系统资源,创建大量线程可能导致过多的资源消耗。
  2. 上下文切换开销: 在线程之间进行切换需要保存和恢复线程的上下文,这可能引起一定的开销。
  3. 共享状态问题: 多线程之间共享内存,可能引发并发访问共享数据的问题,需要使用锁等同步机制来避免竞态条件。

协程(Coroutine)

优势:

  1. 轻量级: 协程是用户级别的线程,相比于操作系统级别的线程,协程更加轻量级。每个goroutine的初始栈大小仅为2KB。它们由用户空间的调度器管理,由Go运行时而不是操作系统调度,goroutine上下文切换代价较小。
  2. 高并发: 协程可以非常高效地进行大量的并发操作,因为它们不受底层系统资源的限制。
  3. 简化并发编程: 由于协程之间可以通过通道(Channel)进行通信,避免了共享内存的并发问题,使得并发编程变得更加简单和安全。
  4. 避免锁: 协程的通信方式通常是通过消息传递,而不是共享内存,避免了锁的使用。

劣势:

  1. 单线程执行: 协程通常在单一线程中执行,这意味着在执行 CPU 密集型任务时性能可能不如多线程。
  2. 操作系统支持不足: 一些传统的操作系统和编程语言可能不直接支持协程,需要使用额外的库或语言特性来实现。
  3. 不充分利用多核: 由于协程通常在单线程中运行,不能充分利用多核 CPU。

总的来说,线程适用于需要充分利用多核 CPU 的计算密集型任务,而协程更适合于需要高并发、I/O 密集型任务,以及对简化并发编程和避免共享状态问题有需求的情境。在实际应用中,它们经常结合使用,以发挥各自的优势。例如,在使用语言如 Go 时,协程(goroutines)和线程可以一起使用,利用 Go 的并发模型来充分发挥多核和高并发的优势。

运作

协程调度器把可运行的协程逐个调度到线程中执行,同时及时把阻塞的协程调度出协程,有效避免了线程的频繁切换,多个协程分享操作系统分给线程的时间片,从而达到了充分利用CPU算力的目的,协程调度器决定了协程运行的顺序。每一时刻一个线程只能运行一个协程

线程模型

线程可以分为用户线程和内核线程,GO主要采用的是M : N模型,M个用户线程(协程)对应N个内核线程,优点就是既充分利用CPU算力且协程上下文切换快,缺点就是调度算法较为复杂

goroutine调度模型与演进过程

首先先明确三个关键实体

machine(M):工作线程,由操作系统调度;M代表着真正的执行计算资源。在绑定有效的P后,进入一个调度循环;而调度循环的机制大致是从各种队列、P的本地运行队列中获取G,切换到G的执行栈上并执行G的函数,调用goexit做清理工作并回到M。如此反复。M并不保留G状态,这是G可以跨M调度的基础。

M的个数通常稍大于P,因为除了运行代码,runtime 包还有其他内置任务需要处理

processor(P):代表逻辑processor,P的数量决定了系统内最大可并行的G的数量(前提:系统的物理CPU核数>=P的数量)。P中最有用的是其拥有的各种G对象队列、链表、一些缓存和状态。

goroutine(G):代表goroutine,存储了goroutine的执行栈信息、goroutine状态及goroutine的任务函数等。另外G对象是可以重用的。

G-M模型

在这个调度器中,每个goroutine对应于运行时中的一个抽象结构——G(goroutine),而被视作“物理CPU”的操作系统线程则被抽象为另一个结构——M(machine)

G-M模型的重要不足:限制了Go并发程序的伸缩性,尤其是对那些有高吞吐或并行计算需求的服务程序,主要存在以下问题:

  1. 单一全局互斥锁(Sched.Lock)和集中状态存储的存在导致所有goroutine相关操作(如创建、重新调度等)都要上锁。
  2. goroutine传递问题:经常在M之间传递“可运行”的goroutine会导致调度延迟增大,带来额外的性能损耗。
  3. 每个M都做内存缓存,导致内存占用过高,数据局部性较差。
  4. 因系统调用(syscall)而形成的频繁的工作线程阻塞和解除阻塞会带来额外的性能损耗。

G-P-M模型

在多CPU环境下,多个处理器往往需要争夺锁来调度全局队列中的协程,严重影响并发执行效率,后来引入局部runqueues,每个处理器访问自己的局部队列时不需要加锁

发现了G-M模型的不足后,Dmitry Vyukov亲自操刀改进了goroutine调度器,在Go 1.1版本中实现了G-P-M调度模型和工作窃取算法,这个模型沿用至今:

在这里插入图片描述

“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。”

向G-M模型中增加了一个P,使得goroutine调度器具有很好的伸缩性。

P是一个“逻辑处理器”,每个G要想真正运行起来,首先需要被分配一个P,即进入P的本地运行队列(local runq)中,全局队列则由多个处理器共享。对于G来说,P就是运行它的“CPU”,可以说在G的眼里只有P。

但从goroutine调度器的视角来看,真正的“CPU”是M,只有将P和M绑定才能让P的本地运行队列中的G真正运行起来。这样的P与M的关系就好比Linux操作系统调度层面用户线程(user thread)与内核线程(kernel thread)的对应关系:多对多(N:M)

一般来说,处理器P中的协程G额外再创建的协程会加人本地的runqueues中,但如果本地的队列已满,或者阻塞的协程被唤醒,则协程会被放入全局的runqueues中,处理器P除了调度本地的runqueues中的协程,还会周期性地从全局runqueues 中摘取协程来调度。

抢占式调度器

之后由于调度器不支持抢占式调度,Dmitry Vyukov又提出了“Go抢占式调度器设计”(Go Preemptive Scheduler Design),并在Go 1.2版本中实现了抢占式调度。

这个抢占式调度的原理是在每个函数或方法的入口加上一段额外的代码,让运行时有机会检查是否需要执行抢占调度。这种协作式抢占调度的解决方案只是局部解决了“饿死”问题,对于没有函数调用而是纯算法循环计算的G,goroutine调度器依然无法抢占

其他优化

在Go 1.2以后,Go将重点放在了对GC低延迟的优化上

Go运行时已经实现了netpoller,这使得即便G发起网络I/O操作也不会导致M被阻塞(仅阻塞G),因而不会导致大量线程(M)被创建出来。但是对于常规文件的I/O操作一旦阻塞,那么线程(M)将进入挂起状态,等待I/O返回后被唤醒。这种情况下P将与挂起的M分离,再选择一个处于空闲状态(idle)的M。如果此时没有空闲的M,则会新创建一个M(线程),这就是大量文件I/O操作会导致大量线程被创建的原因。

增加了一个针对文件I/O的Poller,它可以像netpoller那样,在G操作那些支持监听的(pollable)文件描述符时,仅阻塞G,而不会阻塞M。不过该功能依然对常规文件无效,常规文件是不支持监听的。但对于goroutine调度器而言,这也算是一个不小的进步了

调度策略

队列轮转

每个处理器p维护着一个协程G的队列,处理器P依次将协程 G调度到M中执行。协程G执行结束后,处理器P会再次调度G到M中执行。

同时,每个P会周期性地看全局队列中是否有G待运行并将其调度到 M 中执行,全局队列中的G,主要来自从系统调用中恢复的G。之所以P会周期性地查看全局队列,也是为了防止全局队列中的G长时间得不到调度机会而被“饿死”。

系统调用

我们知道,当线程在执行系统调用时,可能会被阻塞,对应到调度器模型,如果一个协程发起系统调用,那么对应的工作线程会被阻塞,这样一来,处理器的rungueues 队列中的程将得不到调度,相当于队列中的所有协程都被阻塞。 前面提到p的个数默认等于 CPU 的核数,每个 M必须持有一个P才可以执行 G。一般情况下 M的个数会略大于P的个数,多出来的M将会在 G产生系统调用时发挥作用。与线程池类似,Go 也提供一个M的池子,需要时从池子中获取,用完放回池子,不够用时就再创建一个,当M运行的某个 G产生系统调用时,过程如下图所示。
在这里插入图片描述

当G0即将进入系统调用时,M0将释放P,进而某个冗余的M1获取P,继续执行P队列中剩下的G。M0由于陷入系统调用而被阻塞,M1接替M0的工作,只要P不空闲,就可以保证充分地利用CPU。

冗余的M的来源有可能是缓存池,也可能是新建的。当G0结束系统调用后,根据M0是否能获取到P,对G0进行不同的处理:

如果有空闲的P,则获取一个P,继续执行G0。

如果没有空闲的P,则将G0放入全局队列,等待被其他的P调度。然后M0将进入缓存池睡眠。

工作量窃取

通过 go 关键字创建的协程通常会优先放到当前协程对应的处理器队列中,可能有些协程自身不断地派生新的协程,而有些协程不派生协程。如此一来,多个处理器P中维护的G队列有可能是不均衡的,如果不加以控制,则有可能出现部分处理器P非常繁忙,而部分处理器 P怠工的情况。

为此,Go调度器提供了工作量窃取策略,即当某个处理器P没有需要调度的协程时,将从其他处理器中偷取协程,如下图所示。

在这里插入图片描述

发生窃取前(图中左半部分),右侧的处理器在没有协程需要调度时会查询全局队列,如果全局队列中也没有协程需要调度,则会从另一个正在运行的处理器中偷取协程,每次偷 取一半,偷取完的效果如图中右半部分所示。

抢占式调度

所谓抢占式调度,是指避免某个协程长时间执行,而阻碍其他协程被调度的机制。 调度器会监控每个协程的执行时间,一旦执行时间过长且有其他协程在等待时,会把协程暂停,转而调度等待的协程,以达到类似于时间片轮转的效果。 在Go1.14之前,Go协程调度器抢占式调度机制是有一定局限性的,在该设计中,在函数调用间隙检查协程是否可被抢占,如果协程没有函数调用,则会无限期地占用执行权。

直到在Go1.14中,调度器引入了了基于信号的抢占机制,这个问题才得到了解决。

GOMAXPROCS 对性能的影响

一般来讲,程序运行时就将GOMAXPR OCS 的大小设置为CPU 的核数,可让 Go程序充分利用 CPU。在某些I/O密集型的应用中,这个值可能并不意味着性能最好。理论上当某个goroutine进入系统调用时,会有一个新的M被启用或创建,继续占满CPU。但由于Go调度器检测到M被阻塞是有一定延迟的,即旧的M被阻塞和新的M得到运行之间是有一定间隔的,所以在I/O密集型应用中不妨把GOMAXPROCS 的值设置得大一些,或许会有好的效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/136094.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

multilinear多项式承诺方案benchmark对比

1. 引言 前序博客有: Lasso、Jolt 以及 Lookup Singularity——Part 1Lasso、Jolt 以及 Lookup Singularity——Part 2深入了解LassoJolt Lasso lookup中,multilinear多项式承诺方案的高效性至关重要。 本文重点关注4种multilinear多项式承诺方案的实…

【Python基础】try-finally语句和with语句

📢:如果你也对机器人、人工智能感兴趣,看来我们志同道合✨ 📢:不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 📢:文章若有幸对你有帮助,可点赞 👍…

Springboot+vue的毕业生实习与就业管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频: Springbootvue的毕业生实习与就业管理系统(有报告)。Javaee项目,springboot vue前后端分离项目 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点…

logback异步日志打印阻塞工作线程

前言 最新做项目,发现一些历史遗留问题,典型的是日志打印的配置问题,其实都是些简单问题,但是往往简单问题引起严重的事故,比如日志打印阻塞工作线程,以logback和log4j2为例。logback实际上是springboot的…

Smart Link 和 Monitor Link应用

定义 Smart Link常用于双上行链路组网,提高接入的可靠性。 Monitor Link通过监视上行接口,使下行接口同步上行接口状态,起到传递故障信息的作用。 Smart Link,又叫做备份链路。一个Smart Link由两个接口组成,其中一个…

2016年408计网

这一年,计算机网络部分的全部考题都围绕该网络拓扑图进行。 第33题 在 OSI 参考模型中, R1、Switch、Hub 实现的最高功能层分别是() A. 2、2、1 B. 2、2、2 C. 3、2、1 D. 3、2、2 本题考察路由器、以太网交换机、集线器各自实现的最高功能层是什么题目给定R1是…

链表OJ题【环形链表】(3)

目录 环形问题的思考 ❓Q1 ❓Q2 🙂Q2 ❓Q3 ❓Q4 8.环形链表 9.环形链表Ⅱ 今天接着链表的经典问题环形问题。大家一定要自己动手多写写。🙂 快慢指针(保持相对距离/保持相对速度)野指针考虑为NULL的情况带环链表&#x…

Java14新增特性

前言 前面的文章,我们对Java9、Java10、Java11、Java12 、Java13的特性进行了介绍,对应的文章如下 Java9新增特性 Java10新增特性 Java11新增特性 Java12新增特性 Java13新增特性 今天我们来一起看一下Java14这个版本的一些重要信息 版本介绍 Java 14…

No180.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

【图像处理:OpenCV-Python基础操作】

【图像处理:OpenCV-Python基础操作】 1 读取图像2 显示图像3 保存图像4 图像二值化、灰度图、彩色图,像素替换5 通道处理(通道拆分、合并)6 调整尺寸大小7 提取感兴趣区域、掩膜8 乘法、逻辑运算9 HSV色彩空间,获取特定…

【算法每日一练]-单调队列,滑动窗口(保姆级教程 篇1) #滑动窗口 #求m区间的最小值 #理想的正方形 #切蛋糕

今天讲单调队列 目录 题目:滑动窗口 思路: 题目:求m区间的最小值​ 思路: 题目:理想的正方形 思路: 题目:切蛋糕 思路: 一共两种类型:一种是区间中的最值&…

游戏制作:猜数字(1~100),不会也可以先试着玩玩

目录 1 2代码如下:可以试着先玩玩 3运行结果:嘿嘿嘿 4程序分析:想学的看 5总结: 1 猜数范围为1~100,猜大输出猜大了,猜小输出猜小了,游戏可以无限玩。 首先先做一个简单的菜单界面&#xf…

RK3588平台 WIFI的基本概念

一.安卓WIFI框架 Android WIFI系统引入了wpa_supplicant,它的整个WIFI系统以wpa_supplicant为核心来定义上层接口和下层驱动接口。Android WIFI主要分为六大层,分别是WiFi Settings层,Wifi Framework层,Wifi JNI 层, W…

WorkPlus Meet:局域网内部使用的高效视频会议系统

随着全球化和远程办公的趋势,视频会议已成为现代企业和机构不可或缺的沟通工具。而现在,大多数政企单位或者涉密强的企业,都会使用局域网部署的音视频会议系统,提供更高的安全性和隐私保护。因为音视频会议中可能涉及到公司机密和…

Torch Hub 系列#2:VGG 和 ResNet

一、说明 在上一篇教程中,我们了解了 Torch Hub 背后的本质及其概念。然后,我们使用 Torch Hub 的复杂性发布了我们的模型,并通过相同的方式访问它。但是,当我们的工作要求我们利用 Torch Hub 上提供的众多全能模型之一时,会发生什么? 在本教程中,我们将学习如何利用称为…

自动泊车轨迹规划学习

1.基于6次多项式的自动泊车轨迹算法研究 针对常见的自动泊车系统无法躲避障碍物,以及轨迹的曲率不连续等问题进行了泊车轨迹算法的研究以及跟踪算法的设计。 针对低速自动泊车场景进行分析,建立符合对应场景下的车辆运动学模型以及能够泊车的最小车位大…

华为dns mapping配置案例

解决内网PC用公网的dns用域名方法访问公司内网的web服务器: 原理是用DNS mapping方式解决 配置过程:域名——出口公网IP地址——公网端口——协议类型 公司内网client用户填公网dns, 公网上的dns上面已注册有公司对外映射的web服务器的公网…

山西电力市场日前价格预测【2023-11-13】

日前价格预测 预测说明: 如上图所示,预测明日(2023-11-13)山西电力市场全天平均日前电价为428.16元/MWh。其中,最高日前电价为751.89元/MWh,预计出现在18: 30。最低日前电价为289.03元/MWh,预计…

【MySQL系列】 第一章 · MySQL概述

写在前面 Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正&#xff0…

王道 | 数据结构第一章

目录结构 章节总览 1.0 开篇_数据结构在学什么 1.1_1 数据结构的基本概念 1.1_2 数据结构的三要素 1.2_1 算法的基本概念 1.2_2 算法的时间复杂度 1.2_3 算法的空间复杂度 章节总览 1.0 开篇_数据结构在学什么 1.1_1 数据结构的基本概念 数据: 数据是信息的载…