揭秘系列: Goroutine调度器

0febad4a2cea1c1e30f76df00da08533.png

现在不要担心理解上面的图片,因为我们将从非常基础的知识开始。

Goroutines分布在线程中,由Goroutine调度器在幕后处理。根据我们之前的讨论,我们知道一些关于Goroutines的事情:

•从原始执行速度来看,Goroutines不一定比线程更快,因为它们需要一个实际的线程来运行。•Goroutines的真正优势在于上下文切换、内存占用、创建和拆除的成本等方面。

你可能之前听说过Goroutine调度器,但我们真正了解它是如何工作的吗?它是如何将Goroutines与线程配对的?

现在让我们一步一步地分解调度器的操作。

1. Goroutine的M:N调度器

Go团队为我们真正简化了并发处理,想想看:创建一个Goroutine就像在函数前面加上 go 关键字一样容易。

go doWork()

但在这个简单的步骤背后,有一个更深层的系统在运作。

从一开始,Go并不是简单地提供了线程。相反,在中间有一个辅助工具,Goroutine调度器,它是Go运行时的关键部分。

ef25b9e2aabc21c3681d10cfb571c05b.png

1*6UyqGhkbOV7kSlRe1CD-gA.png

那么什么是M:N标签?

它表示Go调度器在将M个Goroutines映射到N个内核线程时所起的作用,形成了M:N模型。你可以拥有更多的操作系统线程,就像可以拥有更多的Goroutines一样。

在我们深入研究调度器之前,让我们澄清一下经常混淆的两个术语:并发和并行。

并发:这是关于同时处理多个任务,它们都在运动,但不总是在同一时刻。•并行:这意味着许多任务在完全相同的时间运行,通常使用多个CPU核心。

0ac5de68d9a2da3d18e01f8d85460334.png

1*30ViMAPkVySvdSDc-hI3sA.png

让我们看看Go调度器是如何与线程配合运作的。

2. PMG 模型

在我们深入研究内部工作原理之前,让我们分解一下P、M和G代表什么。

G(Goroutine)

Goroutine充当Go的最小执行单元,类似于轻量级线程。

在Go的运行时中,它由一个名为gstruct{}表示。一旦创建,它会找到一个逻辑处理器P的本地可运行队列(或G队列)中的位置,然后P将其交给一个实际的内核线程M。

Goroutines通常存在三种主要状态:

等待:在这个阶段,Goroutine停滞不前,可能因为通道或锁之类的操作而暂停,或者可能被系统调用暂停。•可运行:Goroutine已经准备好运行,但尚未开始运行,它正在等待轮到它在一个线程(M)上运行。•运行:现在,Goroutine正在一个线程(M)上积极执行。它会一直执行,直到任务完成,除非调度器中断它或其他原因阻止了它的执行。

bdb07d2b2ca55ba053576dd8bbf80582.png

1*U62eyES6_koQtsv_9jWKHw.png

Goroutines并不仅仅被使用一次然后被丢弃。

相反,当启动新的Goroutine时,Go的运行时会从Goroutine池

中选择一个,但如果找不到任何可用的Goroutine,它会创建一个新的。然后,这个新的Goroutine加入到一个P的可运行队列中。

P(逻辑处理器)

在Go调度器中,当我们提到“处理器”时,我们指的是逻辑实体,而不是物理实体。

默认情况下,P的数量设置为可用的核心数,你可以使用runtime.GOMAXPROCS(int)函数来检查或更改这些处理器的数量。

runtime.GOMAXPROCS(0) // 获取当前允许的逻辑处理器数量

如果你想更改这个数量,最好是在应用程序启动时更改它,如果在运行时更改,它会导致STW(停止一切),直到重新调整处理器。

每个P都拥有自己的可运行Goroutines列表,称为本地运行队列,最多可以容纳256个Goroutines。

42b5a5ece6d4e97ea75149a8079c00d6.png

1*0EneA397HA1uYYeg0_HspQ.png

调度器 — P(逻辑处理器)

如果P的队列达到了最大Coroutines数(256),那么就有一个共享队列,称为全局运行队列,但我们将稍后讨论这个。

"那么 'P' 的这个数量到底代表什么?" 它表示可以同时运行的Goroutines数量 — 想象它们并行运行。

M(机器线程 — 操作系统线程)

一个典型的Go程序最多可以使用1万个线程。

是的,我说的是线程,而不是Goroutines。如果超出这个限制,你可能会使你的Go应用程序崩溃。

"什么情况下会创建一个线程?" 想象一种情况:一个Goroutine处于可运行状态并需要一个线程。如果所有线程已经被阻塞,可能是因为系统调用或非抢占操作,会怎么样?在这种情况下,调度器会介入并为该Goroutine创建一个新线程。一个需要注意的事情是,如果一个线程只是忙于昂贵的计算或长时间运行的任务,它不被视为被卡住或被阻塞。如果你想更改默认线程限制,你可以使用 runtime/debug.SetMaxThreads() 函数,它允许你设置你的Go程序可以使用的操作系统线程的最大数量。此外,值得知道的是,线程是可以重复使用的,因为创建或销毁线程是消耗资源的。

3. MPG 工作原理

让我们通过项目符号逐步了解 M、P 和 G 如何一起运作。

我不会在这里深入讨论每个细节,但我将在即将发布的故事中深入探讨。如果你感兴趣,请订阅。

38a4b92673559be296ffb2fb9a17de6d.png

1*d4hu416FJtHHaJaKJFYkGg.png

Go Scheduler 的工作原理

1.初始化 goroutine:通过使用 go func() 命令,Go Runtime 要么创建一个新的 goroutine,要么从池中选择一个现有的。2.排队位置:goroutine 寻找其在队列中的位置,如果所有逻辑处理器(P)的本地队列都满了,那么这个 goroutine 就被放入全局队列。3.线程配对:这是 M 开始发挥作用的地方。它获取一个 P 并开始处理来自 P 本地队列的 goroutine,当 M 与这个 goroutine 交互时,与之关联的 P 就变得占用,不再可用于其他 M。4.窃取行为:如果某个 P 的队列被耗尽,M 会尝试“借用”另一个 P 队列中一半可运行的 goroutine。如果不成功,它然后检查全局队列,然后再检查网络轮询器(请查看下面的“窃取过程”图表部分)。5.资源分配:M 选择了一个 goroutine(G)之后,它会获取运行 G 所需的所有资源。
a5d29da6ea4075b68af916bde7f7116d.png

“那么被阻塞的线程呢?” 如果一个 goroutine 启动了需要时间的系统调用(比如读取文件),那么 M 会等待。但调度程序不喜欢某个只是坐在那里等待的线程,它会将被暂停的 M 与其 P 解除连接,并将来自队列的另一个可运行的 goroutine 与新的或现有的 M 连接起来,然后与 P 协作。

被阻塞的线程

窃取过程

当一个线程(M)完成了它的任务并没有其他事情可做时,它不会坐在那里。

相反,它积极地寻找更多工作,观察其他处理器并获取它们一半的任务,让我们来详细了解一下:

c65900859fe98d0f313b07b3bcf3ec74.png

ewA.png

1.每 61 个时钟滴答,M 检查全局可运行队列,以确保执行的公平性。如果在全局队列中找到一个可运行的 goroutine,就停止。2.然后,线程 M 检查其本地运行队列,与其处理器 P 相关联,以查看是否有可运行的 goroutine 可以处理。3.如果线程发现它的队列是空的,那么它会查看全局队列,看看那里是否有等待处理的任务。4.然后,线程会检查网络轮询器,以查看是否有与网络相关的任务。5.如果线程在检查了网络轮询器后仍然没有找到任务,它将进入主动搜索模式,我们可以将其视为旋转状态。6.在这种状态下,线程试图从其他处理器的队列中“借用”任务。7.经过所有这些步骤后,如果线程仍然找不到工作,它将停止主动搜索。8.现在,如果有新的任务进来,而且有一个没有在搜索状态的空闲处理器,那么可以提示另一个线程开始工作。

需要注意的细节是全局队列实际上被检查了两次:每 61 个时钟滴答一次以确保公平性,如果本地队列为空,就再次检查。

“如果 M 与其 P 相关联,它怎么能从其他处理器那里获取任务呢?M 会更改其 P 吗?” 答案是不会。即使 M 从另一个 P 的队列中获取任务,它仍然使用其原始处理器来运行该任务。因此,在 M 承担新任务的同时,它仍然忠实于其处理器。

“为什么是 61?” 在设计算法时,特别是哈希算法,通常会选择质数,因为它们除了 1 和它们自己之外没有除数。这可以降低出现模式或规律的机会,从而防止“碰撞”或其他不希望出现的行为。如果太短,系统可能会浪费资源频繁检查全局运行队列。如果太长,goroutine 可能会在执行之前等待过长的时间。

网络轮询器

我们还没有详细讨论网络轮询器,但它在窃取过程图表中提到了。

与 Go Scheduler 一样,网络轮询器是 Go Runtime 的组成部分,负责处理与网络相关的调用(例如,网络 I/O)。

让我们比较两种系统调用类型:

•与网络相关的系统调用:当一个 goroutine 执行网络 I/O 操作时,它不会阻塞线程,而是会在网络轮询器中注册。轮询器会异步等待操作完成,一旦完成,goroutine 就会再次可运行,可以在一个线程上继续执行。•其他系统调用:如果它们可能会阻塞并且不由网络轮询器处理,它们可能会导致 goroutine 将其执行卸载到操作系统线程上。只有特定的操作系统线程会被阻塞,Go 运行时调度程序可以在不同线程上执行其他 goroutine。

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

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

相关文章

Unity中全局光照GI的总结

文章目录 前言一、在编写Shader时,有一些隐蔽的Bug不会直接报错,我们需要编译一下让它显示出来,方便修改我们选择我们的Shader,点击编译并且展示编译后的Shader后的内容,隐蔽的Bug就会暴露出来了。 二、我们大概回顾一…

智慧畜牧小程序开发流程

本文将详细介绍智慧畜牧小程序的开发流程,包括需求分析、设计、开发、测试和上线等环节。同时,将深入思考智慧畜牧小程序的发展趋势和未来挑战,为读者提供有深度的思考和逻辑性的分析。 一、需求分析 1.明确目标用户:首先…

Bond配置文件配置

1、选择2个自己需要的网口,查看有哪些网口 [roothostname ~]# ifconfig -a [roothostname ~]#systemctl disable NetworkManager 开机不启动图形化网络服务 2、编辑网口的配置文件 [roothostname ~]# cd /etc/sysconfig/network-scripts [roothostname n…

实操创建属于自己的亚马逊云科技VPS服务:Amazon Lightsail

本文主要讲述如何独立创建自己的亚马逊云科技VPS服务,希望此文能帮助你对亚马逊云科技VPS服务也就是Amazon Lightsail,有个新的认识,对所使用的VPS有所帮助。 Amazon Lightsail是一款无论云计算的新手还是专家,都可通过其快速启动…

Sagemaker基础操作指南

简介 Amazon SageMaker是亚马逊AWS提供的一项托管式机器学习服务,旨在简化和加速机器学习开发的整个生命周期。它为机器学习工程师和数据科学家提供了一套完整的工具和功能,用于构建、训练、调优和部署机器学习模型。本文将会通过一个简单的例子&#x…

Conda executable is not found 三种问题解决

如果在PyCharm中配置Python解释器时显示“conda executable is not found”错误消息,这意味着PyCharm无法找到您的Conda可执行文件。您可以按照以下步骤解决此问题: 1.方法一 确认Conda已正确安装。请确保您已经正确安装了Anaconda或Miniconda&#xff…

演示文稿制作软件 Deckset mac中文版介绍

Deckset mac是一款Mac上的演示文稿制作软件,它可以让你使用Markdown语言快速地创建演示文稿。与传统的演示文稿制作软件相比,Deckset采用了全新的设计理念,旨在让用户更加专注于内容的创作,而不是花费过多的时间在排版和设计上。 …

vivo 数据库降本实践:探索成本效益最优的数据库解决方案

vivo 自 2022 年开始调研、测试 OceanBase 至今,现已上线 17 个业务系统,涵盖日志类、分析类、交易类业务,实现了总资源节省 80%,开发、运维工作大幅简化。vivo 体系与流程 IT 部门数据库高级工程师廖光明在本文中,详细…

Antd G6实现自定义工具栏

在使用g6实现知识图谱可视化中,产品经理提出了有关图谱操作的不少功能,需要放置在工具栏中,其中有些功能不在g6自带的功能里,且工具栏样式、交互效果也和官方自定义工具栏不同。那我们怎么去实现呢? g6官方的工具栏案例…

香港和美国节点服务器的测试IP哪里有?

在选择服务器时,我们通常需要进行一些测试来评估其性能和稳定性。当然,这其中一个重要的测试指标就是服务器的 IP 地址。通过测试 IP 地址,我们可以了解到服务器所在地区以及网络连接质量等信息。作为香港及亚太数据中心领先服务商恒创科技&a…

解决Python并发访问共享资源引起的竞态条件、死锁、饥饿问题的策略

目录 一、概述 二、竞态条件 三、死锁 四、饥饿 五、总结 一、概述 在Python中,多线程和多进程可以有效地提高程序的并发性能。然而,当多个线程或进程需要访问共享资源时,可能会引发竞态条件、死锁和饥饿等问题。这些问题可能会导致程序…

敏捷战略实施方法-资深组织发展专家实践秘笈

要怎样才能生成敏捷战略呢?作者基于多年的组织发展实践,总结出如下公式:敏捷战略 战略共创 迭代进化 即要得到一个好的敏捷战略,首先要做好战略共创,并在战略实施过程中对战略进行持续迭代,两者不可偏废…

机器学习——奇异值分解案例(图片压缩-代码简洁版)

本想大迈步进入前馈神经网络 但是…唉…瞅了几眼,头晕 然后想到之前梳理的奇异值分解、主成分分析、CBOW都没有实战 如果没有实际操作,会有一种浮在云端的虚无感 但是如果要实际操作,我又不想直接调用库包 可是…如果不直接调包,感…

一种优雅的调用第三方接口的思路及实现

之前的项目调用第三方接口时,往往用HttpUtils类似的静态方法调用。比较丑,不通用。如下,这是截取项目中某人调用的一段代码,非常不雅: 经改进后,采用了动态代理技术来实现,效果如下&#xff1a…

RabbitMQ的 五种工作模型

RabbitMQ 其实一共有六种工作模式: 简单模式(Simple)、工作队列模式(Work Queue)、 发布订阅模式(Publish/Subscribe)、路由模式(Routing)、通配符模式(Topi…

网络安全-黑客技术-小白学习

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高; 二、则是发展相对成熟…

VScode + opencv(cmake编译) + c++ + win配置教程

1、下载opencv 2、下载CMake 3、下载MinGW 放到一个文件夹中 并解压另外两个文件 4、cmake编译opencv 新建文件夹mingw-build 双击cmake-gui 程序会开始自动生成Makefiles等文件配置,需要耐心等待一段时间。 简单总结下:finish->configuring …

【图论实战】 Boost学习 03:dijkstra_shortest_paths

文章目录 示例代码 示例 最短路径: A -> C -> D -> F -> E -> G 长度 16 代码 #include <iostream> #include <boost/graph/adjacency_list.hpp> #include <boost/graph/dijkstra_shortest_paths.hpp> #include <boost/graph/graphviz.h…

状态机实现RGB灯跳变

1.项目功能梗概 因为原本使用的为for循环进行遍历&#xff0c;然后依次执行代码&#xff0c;但是由于看门狗的存在&#xff0c;不能使用delay_ms这种死延时。所以现在打算定时器回调函数控制状态机状态这种方法。 2.状态机 作用 当系统需要执行某个任务时&#xff0c;可以根据…

力扣字符串--总结篇

前言 字符串学了三天&#xff0c;七道题。初窥kmp&#xff0c;已经感受到算法的博大精深了。 内容 对字符串的操作可以归结为以下几类&#xff1a; 字符串的比较、连接操作&#xff08;不同编程语言实现方式有所不同&#xff09;&#xff1b; 涉及子串的操作&#xff0c;比…